diff --git a/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/MetricFilter.java b/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/MetricFilter.java new file mode 100644 index 0000000000..010abe5ec4 --- /dev/null +++ b/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/MetricFilter.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.amoro.metrics.promethues; + +import java.util.Map; +import java.util.regex.Pattern; + +/** Regex-based metric filter for Prometheus exporter. */ +public class MetricFilter { + + public static final String INCLUDES_KEY = "metric-filter.includes"; + public static final String EXCLUDES_KEY = "metric-filter.excludes"; + + public static final MetricFilter ACCEPT_ALL = new MetricFilter(null, null); + + private final Pattern includePattern; + private final Pattern excludePattern; + + public MetricFilter(Pattern includePattern, Pattern excludePattern) { + this.includePattern = includePattern; + this.excludePattern = excludePattern; + } + + /** Parse metric filter from reporter properties. */ + public static MetricFilter fromProperties(Map properties) { + String includes = properties.get(INCLUDES_KEY); + String excludes = properties.get(EXCLUDES_KEY); + + if (includes == null && excludes == null) { + return ACCEPT_ALL; + } + + Pattern includePattern = includes != null ? Pattern.compile(includes) : null; + Pattern excludePattern = excludes != null ? Pattern.compile(excludes) : null; + return new MetricFilter(includePattern, excludePattern); + } + + /** Check if a metric name passes the filter. */ + public boolean matches(String metricName) { + if (includePattern != null && !includePattern.matcher(metricName).matches()) { + return false; + } + if (excludePattern != null && excludePattern.matcher(metricName).matches()) { + return false; + } + return true; + } +} diff --git a/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/MetricsCollector.java b/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/MetricsCollector.java index adc2b75088..9e117c0a5d 100644 --- a/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/MetricsCollector.java +++ b/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/MetricsCollector.java @@ -43,9 +43,15 @@ public class MetricsCollector extends Collector { private static final Pattern NAME_PATTERN = Pattern.compile("[a-zA-Z_:][a-zA-Z0-9_:]*"); private static final Pattern LABEL_PATTERN = Pattern.compile("[a-zA-Z_][a-zA-Z0-9_]*"); MetricSet metrics; + private final MetricFilter metricFilter; public MetricsCollector(MetricSet metrics) { + this(metrics, MetricFilter.ACCEPT_ALL); + } + + public MetricsCollector(MetricSet metrics, MetricFilter metricFilter) { this.metrics = metrics; + this.metricFilter = metricFilter != null ? metricFilter : MetricFilter.ACCEPT_ALL; } @Override @@ -76,8 +82,14 @@ private boolean isValidMetric(MetricDefine define) { boolean valid = nameIsValid && labelIsValid; if (!valid) { LOGGER.warn("Metric {} is not a valid prometheus metric.", define); + return false; } - return valid; + + if (!metricFilter.matches(define.getName())) { + return false; + } + + return true; } private MetricFamilySamples createFamilySample( diff --git a/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/PrometheusMetricsReporter.java b/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/PrometheusMetricsReporter.java index fd8f6f8a81..4ba175f5d0 100644 --- a/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/PrometheusMetricsReporter.java +++ b/amoro-metrics/amoro-metrics-prometheus/src/main/java/org/apache/amoro/metrics/promethues/PrometheusMetricsReporter.java @@ -32,6 +32,7 @@ public class PrometheusMetricsReporter implements MetricReporter { public static final String PORT = "port"; private HTTPServer server; + private MetricFilter metricFilter = MetricFilter.ACCEPT_ALL; @Override public void open(Map properties) { @@ -40,6 +41,8 @@ public void open(Map properties) { .map(Integer::valueOf) .orElseThrow(() -> new IllegalArgumentException("Lack required property: " + PORT)); + this.metricFilter = MetricFilter.fromProperties(properties); + try { this.server = new HTTPServer(port); } catch (IOException e) { @@ -59,7 +62,7 @@ public String name() { @Override public void setGlobalMetricSet(MetricSet globalMetricSet) { - MetricsCollector collector = new MetricsCollector(globalMetricSet); + MetricsCollector collector = new MetricsCollector(globalMetricSet, metricFilter); collector.register(); } } diff --git a/amoro-metrics/amoro-metrics-prometheus/src/test/java/org/apache/amoro/metrics/promethues/MetricFilterTest.java b/amoro-metrics/amoro-metrics-prometheus/src/test/java/org/apache/amoro/metrics/promethues/MetricFilterTest.java new file mode 100644 index 0000000000..7da7f00558 --- /dev/null +++ b/amoro-metrics/amoro-metrics-prometheus/src/test/java/org/apache/amoro/metrics/promethues/MetricFilterTest.java @@ -0,0 +1,105 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.amoro.metrics.promethues; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class MetricFilterTest { + + @Test + public void testAcceptAllMatchesEverything() { + MetricFilter filter = MetricFilter.ACCEPT_ALL; + assertTrue(filter.matches("table_optimizing_status_in_idle")); + assertTrue(filter.matches("optimizer_group_pending_tasks")); + assertTrue(filter.matches("any_metric_name")); + } + + @Test + public void testFromPropertiesEmptyReturnsAcceptAll() { + MetricFilter filter = MetricFilter.fromProperties(Collections.emptyMap()); + assertSame(MetricFilter.ACCEPT_ALL, filter); + } + + @Test + public void testIncludesOnly() { + Map props = new HashMap<>(); + props.put("metric-filter.includes", "table_optimizing_.*|optimizer_group_.*"); + + MetricFilter filter = MetricFilter.fromProperties(props); + assertTrue(filter.matches("table_optimizing_status_in_idle")); + assertTrue(filter.matches("optimizer_group_pending_tasks")); + assertFalse(filter.matches("table_summary_total_files")); + assertFalse(filter.matches("ams_jvm_cpu_load")); + } + + @Test + public void testExcludesOnly() { + Map props = new HashMap<>(); + props.put("metric-filter.excludes", "table_summary_.*"); + + MetricFilter filter = MetricFilter.fromProperties(props); + assertTrue(filter.matches("table_optimizing_status_in_idle")); + assertTrue(filter.matches("ams_jvm_cpu_load")); + assertFalse(filter.matches("table_summary_total_files")); + assertFalse(filter.matches("table_summary_health_score")); + } + + @Test + public void testIncludesAndExcludes() { + Map props = new HashMap<>(); + props.put("metric-filter.includes", "table_.*"); + props.put("metric-filter.excludes", "table_summary_.*"); + + MetricFilter filter = MetricFilter.fromProperties(props); + assertTrue(filter.matches("table_optimizing_status_in_idle")); + assertTrue(filter.matches("table_orphan_content_file_cleaning_count")); + assertFalse(filter.matches("table_summary_total_files")); + assertFalse(filter.matches("optimizer_group_pending_tasks")); + } + + @Test + public void testExcludesTakesPrecedenceOverIncludes() { + Map props = new HashMap<>(); + props.put("metric-filter.includes", ".*"); + props.put("metric-filter.excludes", "ams_jvm_.*"); + + MetricFilter filter = MetricFilter.fromProperties(props); + assertTrue(filter.matches("table_optimizing_status_in_idle")); + assertFalse(filter.matches("ams_jvm_cpu_load")); + assertFalse(filter.matches("ams_jvm_memory_heap_used")); + } + + @Test + public void testExactMatchPattern() { + Map props = new HashMap<>(); + props.put("metric-filter.includes", "ams_jvm_cpu_load"); + + MetricFilter filter = MetricFilter.fromProperties(props); + assertTrue(filter.matches("ams_jvm_cpu_load")); + assertFalse(filter.matches("ams_jvm_cpu_time")); + } +} diff --git a/amoro-metrics/amoro-metrics-prometheus/src/test/java/org/apache/amoro/metrics/promethues/MetricsCollectorRegexFilterTest.java b/amoro-metrics/amoro-metrics-prometheus/src/test/java/org/apache/amoro/metrics/promethues/MetricsCollectorRegexFilterTest.java new file mode 100644 index 0000000000..f50ff503b5 --- /dev/null +++ b/amoro-metrics/amoro-metrics-prometheus/src/test/java/org/apache/amoro/metrics/promethues/MetricsCollectorRegexFilterTest.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.amoro.metrics.promethues; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.prometheus.client.Collector; +import org.apache.amoro.metrics.Counter; +import org.apache.amoro.metrics.Gauge; +import org.apache.amoro.metrics.Metric; +import org.apache.amoro.metrics.MetricDefine; +import org.apache.amoro.metrics.MetricKey; +import org.apache.amoro.metrics.MetricSet; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class MetricsCollectorRegexFilterTest { + + private MetricSet createMetricSet(Map metrics) { + return () -> Collections.unmodifiableMap(metrics); + } + + private void registerMetric(Map metrics, String name, Metric metric) { + MetricDefine.Builder builder; + if (metric instanceof Counter) { + builder = MetricDefine.defineCounter(name); + } else { + builder = MetricDefine.defineGauge(name); + } + MetricDefine define = builder.build(); + MetricKey key = new MetricKey(define, Collections.emptyMap()); + metrics.put(key, metric); + } + + @Test + public void testCollectWithNoFilter() { + Map metrics = new HashMap<>(); + registerMetric(metrics, "table_optimizing_status_in_idle", (Gauge) () -> 1L); + registerMetric(metrics, "optimizer_group_pending_tasks", (Gauge) () -> 5L); + registerMetric(metrics, "ams_jvm_cpu_load", (Gauge) () -> 0.5); + + MetricsCollector collector = new MetricsCollector(createMetricSet(metrics)); + List result = collector.collect(); + + assertEquals(3, result.size()); + } + + @Test + public void testCollectWithIncludesFilter() { + Map metrics = new HashMap<>(); + registerMetric(metrics, "table_optimizing_status_in_idle", (Gauge) () -> 1L); + registerMetric(metrics, "optimizer_group_pending_tasks", (Gauge) () -> 5L); + registerMetric(metrics, "ams_jvm_cpu_load", (Gauge) () -> 0.5); + + MetricFilter filter = new MetricFilter(Pattern.compile("table_optimizing_.*"), null); + MetricsCollector collector = new MetricsCollector(createMetricSet(metrics), filter); + List result = collector.collect(); + + assertEquals(1, result.size()); + assertEquals("amoro_table_optimizing_status_in_idle", result.get(0).name); + } + + @Test + public void testCollectWithExcludesFilter() { + Map metrics = new HashMap<>(); + registerMetric(metrics, "table_optimizing_status_in_idle", (Gauge) () -> 1L); + registerMetric(metrics, "optimizer_group_pending_tasks", (Gauge) () -> 5L); + registerMetric(metrics, "table_summary_total_files", (Gauge) () -> 100L); + + MetricFilter filter = new MetricFilter(null, Pattern.compile("table_summary_.*")); + MetricsCollector collector = new MetricsCollector(createMetricSet(metrics), filter); + List result = collector.collect(); + + assertEquals(2, result.size()); + Set names = result.stream().map(s -> s.name).collect(Collectors.toSet()); + assertFalse(names.contains("amoro_table_summary_total_files")); + assertTrue(names.contains("amoro_table_optimizing_status_in_idle")); + assertTrue(names.contains("amoro_optimizer_group_pending_tasks")); + } + + @Test + public void testCollectWithIncludesAndExcludes() { + Map metrics = new HashMap<>(); + registerMetric(metrics, "table_optimizing_status_in_idle", (Gauge) () -> 1L); + registerMetric(metrics, "table_summary_total_files", (Gauge) () -> 100L); + registerMetric(metrics, "table_orphan_content_file_cleaning_count", new Counter()); + registerMetric(metrics, "optimizer_group_pending_tasks", (Gauge) () -> 5L); + + MetricFilter filter = + new MetricFilter(Pattern.compile("table_.*"), Pattern.compile("table_summary_.*")); + MetricsCollector collector = new MetricsCollector(createMetricSet(metrics), filter); + List result = collector.collect(); + + assertEquals(2, result.size()); + Set names = result.stream().map(s -> s.name).collect(Collectors.toSet()); + assertTrue(names.contains("amoro_table_optimizing_status_in_idle")); + assertTrue(names.contains("amoro_table_orphan_content_file_cleaning_count")); + } + + @Test + public void testCollectWithAcceptAllFilter() { + Map metrics = new HashMap<>(); + registerMetric(metrics, "table_optimizing_status_in_idle", (Gauge) () -> 1L); + registerMetric(metrics, "custom_metric", (Gauge) () -> 42L); + + MetricsCollector collector = + new MetricsCollector(createMetricSet(metrics), MetricFilter.ACCEPT_ALL); + List result = collector.collect(); + + assertEquals(2, result.size()); + } +} diff --git a/charts/amoro/tests/amoro-configmap_test.yaml b/charts/amoro/tests/amoro-configmap_test.yaml index 0c68186936..b6891052a9 100644 --- a/charts/amoro/tests/amoro-configmap_test.yaml +++ b/charts/amoro/tests/amoro-configmap_test.yaml @@ -151,4 +151,18 @@ tests: path: data["metric-reporters.yaml"] pattern: | - name: prometheus-exporter \ No newline at end of file + name: prometheus-exporter + - it: Amoro configMap should reflect metric regex filtering + set: + plugin: + metricReporters: + prometheusExporter: + name: prometheus-exporter + enabled: true + properties: + port: 7001 + metric-filter.excludes: "table_summary_.*" + asserts: + - matchRegex: + path: data["metric-reporters.yaml"] + pattern: "metric-filter.excludes.*table_summary_" \ No newline at end of file diff --git a/charts/amoro/values.yaml b/charts/amoro/values.yaml index 312fefaea0..9334977b0d 100644 --- a/charts/amoro/values.yaml +++ b/charts/amoro/values.yaml @@ -287,7 +287,7 @@ plugin: ## metric reporters ## metricReporters: ~ -## e.g: +## e.g: # metricReporters: # ## @param Configure Prometheus exporter # ## @@ -302,6 +302,10 @@ plugin: # ## @param Prometheus port # ## # port: 7001 +# ## @param Metric filtering by regex (optional, all metrics included by default) +# ## +# metric-filter.includes: "table_optimizing_.*|optimizer_group_.*" +# metric-filter.excludes: "table_summary_.*" ## Configure the ingress resource that allows you to access the ## Amoro installation. Set up the URL diff --git a/dist/src/main/amoro-bin/conf/plugins/metric-reporters.yaml b/dist/src/main/amoro-bin/conf/plugins/metric-reporters.yaml index 40842e8177..07dd778af8 100644 --- a/dist/src/main/amoro-bin/conf/plugins/metric-reporters.yaml +++ b/dist/src/main/amoro-bin/conf/plugins/metric-reporters.yaml @@ -23,4 +23,7 @@ metric-reporters: # enabled: false # properties: # port: 7001 +# # Metric filtering by regex (optional, all metrics included by default) +# # metric-filter.includes: "table_optimizing_.*|optimizer_group_.*" +# # metric-filter.excludes: "table_summary_.*"