io[.]micrometer[.]core[.]instrument[.]MeterRegistry
+ io[.]micrometer[.]core[.]instrument[.]config[.]MeterFilter
io[.]opentelemetry[.]api[.]OpenTelemetry
org[.]apache[.]hadoop[.]io[.]Text
org[.]apache[.]accumulo[.]core[.]client[.].*
diff --git a/core/src/main/java/org/apache/accumulo/core/spi/metrics/LoggingMeterRegistryFactory.java b/core/src/main/java/org/apache/accumulo/core/spi/metrics/LoggingMeterRegistryFactory.java
index 2e24726e869..5d73d5a5563 100644
--- a/core/src/main/java/org/apache/accumulo/core/spi/metrics/LoggingMeterRegistryFactory.java
+++ b/core/src/main/java/org/apache/accumulo/core/spi/metrics/LoggingMeterRegistryFactory.java
@@ -18,14 +18,19 @@
*/
package org.apache.accumulo.core.spi.metrics;
+import static java.util.Objects.requireNonNull;
+
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
+import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.logging.LoggingMeterRegistry;
import io.micrometer.core.instrument.logging.LoggingRegistryConfig;
@@ -49,6 +54,21 @@
*
* general.custom.metrics.opts.logging.step = 10s
*
+ *
+ * To filter out meters that start with any particular prefix, set
+ * {@code general.custom.metrics.opts.meter.id.filters} in the Accumulo configuration.
+ *
+ *
+ * general.custom.metrics.opts.meter.id.filters = prefix1,prefix2,prefix3
+ *
+ *
+ *
+ * To assign a custom delimiter to filters, set
+ * {@code general.custom.metrics.opts.meter.id.delimiter} in the Accumulo configuration.
+ *
+ *
+ * general.custom.metrics.opts.meter.id.delimiter = delimiter
+ *
*/
public class LoggingMeterRegistryFactory implements MeterRegistryFactory {
@@ -76,6 +96,49 @@ public MeterRegistry create(final InitParameters params) {
LOG.info("Creating logging metrics registry with params: {}", params);
metricsProps.putAll(params.getOptions());
- return LoggingMeterRegistry.builder(lconf).loggingSink(metricConsumer).build();
+
+ MeterRegistry registry =
+ LoggingMeterRegistry.builder(lconf).loggingSink(metricConsumer).build();
+ String filters = metricsProps.get("meter.id.filters");
+ // Sets the default delimiter if none is specified
+ String delimiter = metricsProps.getOrDefault("meter.id.delimiter", ",");
+
+ if (filters != null && delimiter != null) {
+ registry.config().meterFilter(getMeterFilter(filters, delimiter));
+ }
+ return registry;
+ }
+
+ /**
+ * This function uses terms specified in the patternList parameter to filter out specific metrics
+ * that the user doesn't want.
+ *
+ * @param patternList a delimited set of terms that will filter out meters that start with any one
+ * of those terms.
+ * @param delimiter that contains either a user specified delimiter, or the default comma
+ * delimiter to split the patternList.
+ * @return a predicate with the type of MeterFilter, that describes which metrics to deny and
+ * subsequently filter out.
+ */
+ public static MeterFilter getMeterFilter(String patternList, String delimiter) {
+ requireNonNull(patternList, "patternList must not be null");
+
+ // Trims whitespace and all other non-visible characters.
+ patternList = patternList.replaceAll("\\s+", "");
+
+ // Gets the default delimiter or the delimiter the user supplied
+ String[] patterns = patternList.split(delimiter);
+ Predicate finalPredicate = id -> false;
+
+ if (patternList.isEmpty()) {
+ return MeterFilter.deny(finalPredicate);
+ }
+
+ for (String prefix : patterns) {
+ Predicate predicate = id -> id.getName().startsWith(prefix);
+ finalPredicate = finalPredicate.or(predicate);
+
+ }
+ return MeterFilter.deny(finalPredicate);
}
}
diff --git a/core/src/test/java/org/apache/accumulo/core/spi/metrics/LoggingMeterRegistryFactoryTest.java b/core/src/test/java/org/apache/accumulo/core/spi/metrics/LoggingMeterRegistryFactoryTest.java
index 9d628d27752..f57b5abe7cd 100644
--- a/core/src/test/java/org/apache/accumulo/core/spi/metrics/LoggingMeterRegistryFactoryTest.java
+++ b/core/src/test/java/org/apache/accumulo/core/spi/metrics/LoggingMeterRegistryFactoryTest.java
@@ -18,13 +18,17 @@
*/
package org.apache.accumulo.core.spi.metrics;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import java.util.List;
import java.util.Map;
import org.apache.accumulo.core.spi.common.ServiceEnvironment;
import org.junit.jupiter.api.Test;
+import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.logging.LoggingMeterRegistry;
class LoggingMeterRegistryFactoryTest {
@@ -32,16 +36,24 @@ class LoggingMeterRegistryFactoryTest {
@Test
public void createTest() {
LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
- var reg = factory.create(new LoggingMetricsParams());
+ var reg =
+ factory.create(new LoggingMetricsParams(Map.of("prop1", "abc", "logging.step", "1s")));
assertInstanceOf(LoggingMeterRegistry.class, reg);
}
private static class LoggingMetricsParams implements MeterRegistryFactory.InitParameters {
+ private final Map options;
+
+ public LoggingMetricsParams(Map options) {
+ this.options = options;
+ }
+
@Override
public Map getOptions() {
+ return options;
// note: general.custom.metrics.opts. is expected to be stripped before passing the options.
- return Map.of("prop1", "abc", "logging.step", "1s");
+ // return Map.of("prop1", "abc", "logging.step", "1s", "meter.id.filters", "Foo,Bar,Hat");
}
@Override
@@ -49,4 +61,107 @@ public ServiceEnvironment getServiceEnv() {
return null;
}
}
+
+ /**
+ * Create a factory and test that the given filters are applied to all ID's that match the given
+ * meter.id.filters.
+ */
+ @Test
+ public void createWithFilters() {
+ LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
+ var reg = factory.create(new LoggingMetricsParams(
+ Map.of("prop1", "abc", "logging.step", "1s", "meter.id.filters", "Foo,Bar,Hat")));
+ reg.timer("FooTimer", "tag1", "tag2"); // Yes filters this out
+ reg.timer("ProperTimer", "tag1", "tag2");
+
+ List meterReg = reg.getMeters(); // Does this only contain ProperTimer? Yes
+ assertEquals("ProperTimer", meterReg.get(0).getId().getName());
+ }
+
+ /**
+ * Verify that the filters can be split by either the default or user-specified delimiter.
+ */
+ @Test
+ public void testNonCommaDelimiter() {
+ LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
+ var reg = factory.create(new LoggingMetricsParams(Map.of("prop1", "abc", "logging.step", "1s",
+ "meter.id.filters", "Foo:Bar:Hat", "meter.id.delimiter", ":")));
+ reg.timer("FooTimer", "tag1", "tag2");
+ reg.timer("ProperTimer", "tag1", "tag2");
+ reg.timer("BarTimer", "tag1", "tag2");
+ reg.timer("HatTimer", "tag1", "tag2");
+ reg.gauge("BarGauge", 10);
+ reg.gauge("ProperGauge", 10);
+ reg.counter("HatCounter", "tag1", "tag2");
+ reg.counter("ProperCounter", "tag1", "tag2");
+
+ List meterReg = reg.getMeters();
+ StringBuilder allMeters = new StringBuilder();
+ for (Meter m : meterReg) {
+ allMeters.append(m.getId().getName());
+ }
+
+ assertFalse(allMeters.toString().contains("Foo"));
+ assertFalse(allMeters.toString().contains("Bar"));
+ assertFalse(allMeters.toString().contains("Hat"));
+ }
+
+ /**
+ * Verify that {@link LoggingMeterRegistryFactory#getMeterFilter(String, String)} only filters out
+ * meters where the filter terms are prefixes for the ID.
+ */
+ @Test
+ public void testIfPatternIsNotFirst() {
+ LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
+ var reg = factory.create(new LoggingMetricsParams(
+ Map.of("prop1", "abc", "logging.step", "1s", "meter.id.filters", "Foo,Bar,Hat")));
+ reg.timer("FooTimer", "tag1", "tag2");
+ reg.timer("TimerFoo", "tag1", "tag2");
+
+ List meterReg = reg.getMeters();
+ StringBuilder allMeters = new StringBuilder();
+ for (Meter m : meterReg) {
+ allMeters.append(m.getId().getName());
+ }
+ assertEquals("TimerFoo", allMeters.toString());
+ }
+
+ /**
+ * Test that {@link LoggingMeterRegistryFactory#getMeterFilter(String, String)} can filter out
+ * meters correctly even when given duplicates.
+ */
+ @Test
+ public void testIfDuplicatePatterns() {
+ LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
+ var reg = factory.create(new LoggingMetricsParams(
+ Map.of("prop1", "abc", "logging.step", "1s", "meter.id.filters", "Foo,Foo,Foo")));
+ reg.timer("FooTimer", "tag1", "tag2");
+
+ List meterReg = reg.getMeters();
+ StringBuilder allMeters = new StringBuilder();
+ for (Meter m : meterReg) {
+ allMeters.append(m.getId().getName());
+ }
+
+ assertEquals("", allMeters.toString());
+ }
+
+ /**
+ * Test that when {@link LoggingMeterRegistryFactory#getMeterFilter(String, String)} is given an
+ * empty string of filters, all IDs are still passed through.
+ */
+ @Test
+ public void testEmptyPatternList() {
+ LoggingMeterRegistryFactory factory = new LoggingMeterRegistryFactory();
+ var reg = factory.create(new LoggingMetricsParams(
+ Map.of("prop1", "abc", "logging.step", "1s", "meter.id.filters", "")));
+ reg.timer("FooTimer", "tag1", "tag2");
+ List meterReg = reg.getMeters();
+ StringBuilder allMeters = new StringBuilder();
+ for (Meter m : meterReg) {
+ allMeters.append(m.getId().getName());
+ }
+ assertEquals("FooTimer", allMeters.toString());
+ }
+
}