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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/content/getting-started/registry.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,27 @@ and full label-schema validation and duplicate detection still apply. A collecto
non-null type but leaves `getLabelNames()` as `null` is still validated, with its labels treated as
empty.

## Filtering Metrics

You can set a registry-level metric name filter that applies to all scrape operations.
Only metrics whose names match the filter predicate will be included in scrape results:

```java
PrometheusRegistry.defaultRegistry.setMetricFilter(name -> !name.startsWith("debug_"));
```

The registry filter is AND-combined with any scrape-time `includedNames` predicate passed to
`scrape(Predicate)`. For example, if the registry filter allows `counter_*` and the scrape-time
filter allows `counter_a`, only `counter_a` will be included.

To remove the filter, set it to `null`:

```java
PrometheusRegistry.defaultRegistry.setMetricFilter(null);
```

Note that `clear()` does not reset the metric filter -- it only removes registered collectors.

## Unregistering a Metric

There is no automatic expiry of unused metrics (yet), once a metric is registered it will remain
Expand Down
3 changes: 2 additions & 1 deletion mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ env.PROTO_GENERATION = "true"

[tasks.test]
description = "run unit tests, ignoring formatting and linters"
run = "./mvnw test -Dspotless.check.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true -Dwarnings=-nowarn"
usage = 'arg "[args]" var=#true help="Extra Maven arguments (e.g. -pl prometheus-metrics-model)"'
run = "./mvnw test -Dspotless.check.skip=true -Dcoverage.skip=true -Dcheckstyle.skip=true -Dwarnings=-nowarn ${usage_args:-}"

[tasks.test-all]
description = "run all tests"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public class PrometheusRegistry {
new ConcurrentHashMap<>();
private final ConcurrentHashMap<MultiCollector, List<MultiCollectorRegistration>>
multiCollectorMetadata = new ConcurrentHashMap<>();
private @Nullable Predicate<String> metricFilter;

/** Stores the registration details for a Collector at registration time. */
private static class CollectorRegistration {
Expand Down Expand Up @@ -302,11 +303,44 @@ public void clear() {
multiCollectorMetadata.clear();
}

/**
* Sets a registry-level metric name filter. When set, only metrics whose names match the
* predicate will be included in scrape results. This filter is AND-combined with any scrape-time
* {@code includedNames} predicate.
*
* @param metricFilter the filter predicate, or {@code null} to remove the filter
*/
public void setMetricFilter(@Nullable Predicate<String> metricFilter) {
this.metricFilter = metricFilter;
}

/** Returns the current registry-level metric name filter, or {@code null} if none is set. */
@Nullable
public Predicate<String> getMetricFilter() {
return metricFilter;
}

@Nullable
private Predicate<String> effectiveFilter(@Nullable Predicate<String> includedNames) {
Predicate<String> registryFilter = this.metricFilter;
if (registryFilter != null && includedNames != null) {
return registryFilter.and(includedNames);
} else if (registryFilter != null) {
return registryFilter;
} else {
return includedNames;
}
}

public MetricSnapshots scrape() {
return scrape((PrometheusScrapeRequest) null);
}

public MetricSnapshots scrape(@Nullable PrometheusScrapeRequest scrapeRequest) {
Predicate<String> filter = effectiveFilter(null);
if (filter != null) {
return scrape(filter, scrapeRequest);
}
List<MetricSnapshot> allSnapshots = new ArrayList<>();
for (Collector collector : collectors) {
MetricSnapshot snapshot =
Expand All @@ -331,27 +365,29 @@ public MetricSnapshots scrape(@Nullable PrometheusScrapeRequest scrapeRequest) {
}

public MetricSnapshots scrape(Predicate<String> includedNames) {
if (includedNames == null) {
return scrape();
Predicate<String> filter = effectiveFilter(includedNames);
if (filter == null) {
return scrape((PrometheusScrapeRequest) null);
}
return scrape(includedNames, null);
return scrape(filter, null);
}

public MetricSnapshots scrape(
Predicate<String> includedNames, @Nullable PrometheusScrapeRequest scrapeRequest) {
if (includedNames == null) {
Predicate<String> filter = effectiveFilter(includedNames);
if (filter == null) {
return scrape(scrapeRequest);
}
List<MetricSnapshot> allSnapshots = new ArrayList<>();
for (Collector collector : collectors) {
String prometheusName = collector.getPrometheusName();
// prometheusName == null means the name is unknown, and we have to scrape to learn the name.
// prometheusName != null means we can skip the scrape if the name is excluded.
if (prometheusName == null || includedNames.test(prometheusName)) {
if (prometheusName == null || filter.test(prometheusName)) {
MetricSnapshot snapshot =
scrapeRequest == null
? collector.collect(includedNames)
: collector.collect(includedNames, scrapeRequest);
? collector.collect(filter)
: collector.collect(filter, scrapeRequest);
if (snapshot != null) {
allSnapshots.add(snapshot);
}
Expand All @@ -365,16 +401,16 @@ public MetricSnapshots scrape(
// the filter.
boolean excluded = !prometheusNames.isEmpty();
for (String prometheusName : prometheusNames) {
if (includedNames.test(prometheusName)) {
if (filter.test(prometheusName)) {
excluded = false;
break;
}
}
if (!excluded) {
MetricSnapshots snapshots =
scrapeRequest == null
? collector.collect(includedNames)
: collector.collect(includedNames, scrapeRequest);
? collector.collect(filter)
: collector.collect(filter, scrapeRequest);
for (MetricSnapshot snapshot : snapshots) {
if (snapshot != null) {
allSnapshots.add(snapshot);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ public Set<String> getLabelNames() {
.hasMessageContaining("duplicate metric name with identical label schema");

// Only the first collector should be in the registry; counter2 was removed on rollback.
assertThat(registry.scrape().size()).isEqualTo(1);
assertThat(registry.scrape().size()).isOne();
}

@Test
Expand Down Expand Up @@ -999,12 +999,12 @@ public Set<String> getLabelNames() {
// Unregister first collector - name should still be registered
registry.unregister(counter1);
MetricSnapshots snapshots = registry.scrape();
assertThat(snapshots.size()).isEqualTo(1);
assertThat(snapshots.size()).isOne();

// Unregister second collector - name should be removed
registry.unregister(counter2);
snapshots = registry.scrape();
assertThat(snapshots.size()).isEqualTo(0);
assertThat(snapshots.size()).isZero();

// Should be able to register again with same name
assertThatCode(() -> registry.register(counter1)).doesNotThrowAnyException();
Expand Down Expand Up @@ -1038,7 +1038,7 @@ public MetricType getMetricType(String prometheusName) {
assertThat(registry.scrape().size()).isEqualTo(2);

registry.unregister(multi);
assertThat(registry.scrape().size()).isEqualTo(0);
assertThat(registry.scrape().size()).isZero();

// Should be able to register collectors with same names again
Collector counter =
Expand All @@ -1062,6 +1062,98 @@ public MetricType getMetricType() {
assertThatCode(() -> registry.register(counter)).doesNotThrowAnyException();
}

@Test
void metricFilter_filtersOnScrape() {
PrometheusRegistry registry = new PrometheusRegistry();
registry.register(counterA1);
registry.register(counterB);
registry.register(gaugeA);

registry.setMetricFilter(name -> name.startsWith("counter"));

MetricSnapshots snapshots = registry.scrape();
assertThat(snapshots.size()).isEqualTo(2);
}

@Test
void metricFilter_combinedWithScrapeTimeFilter() {
PrometheusRegistry registry = new PrometheusRegistry();
registry.register(counterA1);
registry.register(counterB);
registry.register(gaugeA);

registry.setMetricFilter(name -> name.startsWith("counter"));
MetricSnapshots snapshots = registry.scrape(name -> name.equals("counter_a"));
assertThat(snapshots.size()).isOne();
}

@Test
void metricFilter_nullClearsFilter() {
PrometheusRegistry registry = new PrometheusRegistry();
registry.register(counterA1);
registry.register(counterB);
registry.register(gaugeA);

registry.setMetricFilter(name -> name.startsWith("counter"));
assertThat(registry.scrape().size()).isEqualTo(2);

registry.setMetricFilter(null);
assertThat(registry.scrape().size()).isEqualTo(3);
}

@Test
void metricFilter_appliedToScrapeWithScrapeRequest() {
PrometheusRegistry registry = new PrometheusRegistry();
registry.register(counterA1);
registry.register(counterB);
registry.register(gaugeA);

registry.setMetricFilter(name -> name.startsWith("counter"));

MetricSnapshots snapshots = registry.scrape((PrometheusScrapeRequest) null);
assertThat(snapshots.size()).isEqualTo(2);
}

@Test
void metricFilter_appliedToMultiCollector() {
PrometheusRegistry registry = new PrometheusRegistry();
registry.register(counterA1);
registry.register(multiCollector);

registry.setMetricFilter(name -> name.equals("counter_a"));

MetricSnapshots snapshots = registry.scrape();
assertThat(snapshots.size()).isOne();
}

@Test
void metricFilter_noNameCollector_alwaysScraped() {
PrometheusRegistry registry = new PrometheusRegistry();
registry.register(noName);
registry.register(counterA1);

// Filter matches both "counter_a" and "no_name_gauge" (the snapshot name of noName).
// noName has null getPrometheusName(), so the registry always calls collect(filter) on it.
// The collector's own collect(Predicate) then tests the snapshot name against the filter.
registry.setMetricFilter(name -> name.equals("counter_a") || name.equals("no_name_gauge"));

MetricSnapshots snapshots = registry.scrape();
assertThat(snapshots.size()).isEqualTo(2);
}

@Test
void metricFilter_excludesAll() {
PrometheusRegistry registry = new PrometheusRegistry();
registry.register(counterA1);
registry.register(counterB);
registry.register(gaugeA);

registry.setMetricFilter(name -> false);

MetricSnapshots snapshots = registry.scrape();
assertThat(snapshots.size()).isZero();
}

@Test
void unregister_legacyCollector_noErrors() {
PrometheusRegistry registry = new PrometheusRegistry();
Expand All @@ -1081,10 +1173,10 @@ public String getPrometheusName() {
};

registry.register(legacy);
assertThat(registry.scrape().size()).isEqualTo(1);
assertThat(registry.scrape().size()).isOne();

// Unregister should work without errors even for legacy collectors
assertThatCode(() -> registry.unregister(legacy)).doesNotThrowAnyException();
assertThat(registry.scrape().size()).isEqualTo(0);
assertThat(registry.scrape().size()).isZero();
}
}