Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -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<String, String> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> properties) {
Expand All @@ -40,6 +41,8 @@ public void open(Map<String, String> 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) {
Expand All @@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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"));
}
}
Original file line number Diff line number Diff line change
@@ -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<MetricKey, Metric> metrics) {
return () -> Collections.unmodifiableMap(metrics);
}

private void registerMetric(Map<MetricKey, Metric> 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<MetricKey, Metric> metrics = new HashMap<>();
registerMetric(metrics, "table_optimizing_status_in_idle", (Gauge<Long>) () -> 1L);
registerMetric(metrics, "optimizer_group_pending_tasks", (Gauge<Long>) () -> 5L);
registerMetric(metrics, "ams_jvm_cpu_load", (Gauge<Double>) () -> 0.5);

MetricsCollector collector = new MetricsCollector(createMetricSet(metrics));
List<Collector.MetricFamilySamples> result = collector.collect();

assertEquals(3, result.size());
}

@Test
public void testCollectWithIncludesFilter() {
Map<MetricKey, Metric> metrics = new HashMap<>();
registerMetric(metrics, "table_optimizing_status_in_idle", (Gauge<Long>) () -> 1L);
registerMetric(metrics, "optimizer_group_pending_tasks", (Gauge<Long>) () -> 5L);
registerMetric(metrics, "ams_jvm_cpu_load", (Gauge<Double>) () -> 0.5);

MetricFilter filter = new MetricFilter(Pattern.compile("table_optimizing_.*"), null);
MetricsCollector collector = new MetricsCollector(createMetricSet(metrics), filter);
List<Collector.MetricFamilySamples> result = collector.collect();

assertEquals(1, result.size());
assertEquals("amoro_table_optimizing_status_in_idle", result.get(0).name);
}

@Test
public void testCollectWithExcludesFilter() {
Map<MetricKey, Metric> metrics = new HashMap<>();
registerMetric(metrics, "table_optimizing_status_in_idle", (Gauge<Long>) () -> 1L);
registerMetric(metrics, "optimizer_group_pending_tasks", (Gauge<Long>) () -> 5L);
registerMetric(metrics, "table_summary_total_files", (Gauge<Long>) () -> 100L);

MetricFilter filter = new MetricFilter(null, Pattern.compile("table_summary_.*"));
MetricsCollector collector = new MetricsCollector(createMetricSet(metrics), filter);
List<Collector.MetricFamilySamples> result = collector.collect();

assertEquals(2, result.size());
Set<String> 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<MetricKey, Metric> metrics = new HashMap<>();
registerMetric(metrics, "table_optimizing_status_in_idle", (Gauge<Long>) () -> 1L);
registerMetric(metrics, "table_summary_total_files", (Gauge<Long>) () -> 100L);
registerMetric(metrics, "table_orphan_content_file_cleaning_count", new Counter());
registerMetric(metrics, "optimizer_group_pending_tasks", (Gauge<Long>) () -> 5L);

MetricFilter filter =
new MetricFilter(Pattern.compile("table_.*"), Pattern.compile("table_summary_.*"));
MetricsCollector collector = new MetricsCollector(createMetricSet(metrics), filter);
List<Collector.MetricFamilySamples> result = collector.collect();

assertEquals(2, result.size());
Set<String> 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<MetricKey, Metric> metrics = new HashMap<>();
registerMetric(metrics, "table_optimizing_status_in_idle", (Gauge<Long>) () -> 1L);
registerMetric(metrics, "custom_metric", (Gauge<Long>) () -> 42L);

MetricsCollector collector =
new MetricsCollector(createMetricSet(metrics), MetricFilter.ACCEPT_ALL);
List<Collector.MetricFamilySamples> result = collector.collect();

assertEquals(2, result.size());
}
}
16 changes: 15 additions & 1 deletion charts/amoro/tests/amoro-configmap_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -151,4 +151,18 @@ tests:
path: data["metric-reporters.yaml"]
pattern:
|
name: prometheus-exporter
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_"
Loading
Loading