This is an automated email from the ASF dual-hosted git repository. frankgh pushed a commit to branch trunk in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git
The following commit(s) were added to refs/heads/trunk by this push: new 9a268ace CASSANDRASC-115 Add FilteringMetricRegistry to allow filtering of metrics (#109) 9a268ace is described below commit 9a268ace28e5864f443ce0cb2768974b5b12fd3a Author: Saranya Krishnakumar <sarany...@apple.com> AuthorDate: Wed Apr 10 20:49:29 2024 -0700 CASSANDRASC-115 Add FilteringMetricRegistry to allow filtering of metrics (#109) Patch by Saranya Krishnakumar; Reviewed by Yifan Cai, Francisco Guerrero for CASSANDRASC-115 --- CHANGES.txt | 1 + src/main/dist/conf/sidecar.yaml | 10 +- .../cluster/instance/InstanceMetadataImpl.java | 23 +-- .../sidecar/config/MetricsConfiguration.java | 12 ++ ...ion.java => MetricsFilteringConfiguration.java} | 12 +- .../config/yaml/MetricsConfigurationImpl.java | 36 +++- .../yaml/MetricsFilteringConfigurationImpl.java | 82 ++++++++ .../sidecar/metrics/FilteringMetricRegistry.java | 225 +++++++++++++++++++++ .../cassandra/sidecar/metrics/MetricFilter.java | 97 +++++++++ .../sidecar/metrics/MetricRegistryFactory.java | 103 ++++++++++ .../cassandra/sidecar/server/MainModule.java | 22 +- .../testing/CassandraSidecarTestContext.java | 7 +- .../sidecar/config/SidecarConfigurationTest.java | 51 ++++- .../metrics/FilteringMetricRegistryTest.java | 213 +++++++++++++++++++ .../cassandra/sidecar/snapshots/SnapshotUtils.java | 10 +- .../resources/config/sidecar_invalid_metrics.yaml | 47 +++++ src/test/resources/config/sidecar_metrics.yaml | 83 +++++--- 17 files changed, 960 insertions(+), 74 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7a882552..43873923 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,6 @@ 1.0.0 ----- + * Add FilteringMetricRegistry to allow filtering of metrics (CASSANDRASC-115) * Allow for JmxClient to be extensible (CASSANDRASC-116) * Improve observability in Sidecar (CASSANDRASC-111) * Improve logging for slice restore task (CASSANDRASC-107) diff --git a/src/main/dist/conf/sidecar.yaml b/src/main/dist/conf/sidecar.yaml index 2369f252..6d99f1db 100644 --- a/src/main/dist/conf/sidecar.yaml +++ b/src/main/dist/conf/sidecar.yaml @@ -148,8 +148,14 @@ metrics: enabled: true expose_via_jmx: false jmx_domain_name: sidecar.vertx.jmx_domain - monitored_server_route_regexes: # regex list to match server routes - - /api/v1/.* + include: # empty include list means include all + - type: "regex" # possible filter types are "regex" and "equals" + value: "sidecar.*" + - type: "regex" + value: "vertx.*" + exclude: # empty exclude list means exclude nothing +# - type: "regex" # possible filter types are "regex" and "equals" +# value: "vertx.eventbus.*" # exclude all metrics starts with vertx.eventbus cassandra_input_validation: forbidden_keyspaces: diff --git a/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java b/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java index d0e4fd97..1f909332 100644 --- a/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java +++ b/src/main/java/org/apache/cassandra/sidecar/cluster/instance/InstanceMetadataImpl.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Objects; import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.SharedMetricRegistries; import org.apache.cassandra.sidecar.cluster.CassandraAdapterDelegate; import org.apache.cassandra.sidecar.common.DataObjectBuilder; import org.apache.cassandra.sidecar.metrics.instance.InstanceMetrics; @@ -114,7 +113,7 @@ public class InstanceMetadataImpl implements InstanceMetadata protected List<String> dataDirs; protected String stagingDir; protected CassandraAdapterDelegate delegate; - protected String globalRegistryName; + protected MetricRegistry metricRegistry; protected InstanceMetrics metrics; protected Builder() @@ -205,15 +204,14 @@ public class InstanceMetadataImpl implements InstanceMetadata } /** - * Sets the {@code globalRegistryName} and returns a reference to this Builder enabling method chaining. + * Sets the {@code metricRegistry} and returns a reference to this Builder enabling method chaining. * - * @param registryName global {@link com.codahale.metrics.MetricRegistry} name + * @param metricRegistry instance specific metric registry * @return a reference to this Builder */ - public Builder globalMetricRegistryName(String registryName) - + public Builder metricRegistry(MetricRegistry metricRegistry) { - return update(b -> b.globalRegistryName = registryName); + return update(b -> b.metricRegistry = metricRegistry); } /** @@ -225,18 +223,11 @@ public class InstanceMetadataImpl implements InstanceMetadata public InstanceMetadataImpl build() { Objects.requireNonNull(id); - Objects.requireNonNull(globalRegistryName); + Objects.requireNonNull(metricRegistry); - String instanceRegistryName = instanceRegistryName(globalRegistryName); - MetricRegistry instanceMetricRegistry = SharedMetricRegistries.getOrCreate(instanceRegistryName); - metrics = new InstanceMetricsImpl(instanceMetricRegistry); + metrics = new InstanceMetricsImpl(metricRegistry); return new InstanceMetadataImpl(this); } - - private String instanceRegistryName(String globalRegistryName) - { - return globalRegistryName + "_" + id; - } } } diff --git a/src/main/java/org/apache/cassandra/sidecar/config/MetricsConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/MetricsConfiguration.java index 2fc622a0..22518fed 100644 --- a/src/main/java/org/apache/cassandra/sidecar/config/MetricsConfiguration.java +++ b/src/main/java/org/apache/cassandra/sidecar/config/MetricsConfiguration.java @@ -18,6 +18,8 @@ package org.apache.cassandra.sidecar.config; +import java.util.List; + /** * Configuration needed for capturing metrics. */ @@ -32,4 +34,14 @@ public interface MetricsConfiguration * @return configuration needed for capturing metrics released by Vert.x framework. */ VertxMetricsConfiguration vertxConfiguration(); + + /** + * @return filters for metrics to be recorded + */ + List<MetricsFilteringConfiguration> includeConfigurations(); + + /** + * @return filters for excluding metrics during capture + */ + List<MetricsFilteringConfiguration> excludeConfigurations(); } diff --git a/src/main/java/org/apache/cassandra/sidecar/config/MetricsConfiguration.java b/src/main/java/org/apache/cassandra/sidecar/config/MetricsFilteringConfiguration.java similarity index 73% copy from src/main/java/org/apache/cassandra/sidecar/config/MetricsConfiguration.java copy to src/main/java/org/apache/cassandra/sidecar/config/MetricsFilteringConfiguration.java index 2fc622a0..b64151e5 100644 --- a/src/main/java/org/apache/cassandra/sidecar/config/MetricsConfiguration.java +++ b/src/main/java/org/apache/cassandra/sidecar/config/MetricsFilteringConfiguration.java @@ -19,17 +19,17 @@ package org.apache.cassandra.sidecar.config; /** - * Configuration needed for capturing metrics. + * Configuration needed for filtering metrics captured. */ -public interface MetricsConfiguration +public interface MetricsFilteringConfiguration { /** - * @return global registry name to be used for registering sidecar metrics + * @return type of metric filter. Possible values are regex, equals. */ - String registryName(); + String type(); /** - * @return configuration needed for capturing metrics released by Vert.x framework. + * @return pattern supported by the filter. */ - VertxMetricsConfiguration vertxConfiguration(); + String value(); } diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/MetricsConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/MetricsConfigurationImpl.java index fc60a6d2..53a4d0a2 100644 --- a/src/main/java/org/apache/cassandra/sidecar/config/yaml/MetricsConfigurationImpl.java +++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/MetricsConfigurationImpl.java @@ -18,8 +18,12 @@ package org.apache.cassandra.sidecar.config.yaml; +import java.util.Collections; +import java.util.List; + import com.fasterxml.jackson.annotation.JsonProperty; import org.apache.cassandra.sidecar.config.MetricsConfiguration; +import org.apache.cassandra.sidecar.config.MetricsFilteringConfiguration; import org.apache.cassandra.sidecar.config.VertxMetricsConfiguration; /** @@ -35,16 +39,26 @@ public class MetricsConfigurationImpl implements MetricsConfiguration protected final String registryName; @JsonProperty(value = "vertx") protected final VertxMetricsConfiguration vertxConfiguration; + @JsonProperty(value = "include") + protected final List<MetricsFilteringConfiguration> includeConfigurations; + @JsonProperty(value = "exclude") + protected final List<MetricsFilteringConfiguration> excludeConfigurations; public MetricsConfigurationImpl() { - this(DEFAULT_DROPWIZARD_REGISTRY_NAME, DEFAULT_VERTX_METRICS_CONFIGURATION); + this(DEFAULT_DROPWIZARD_REGISTRY_NAME, DEFAULT_VERTX_METRICS_CONFIGURATION, + Collections.emptyList(), Collections.emptyList()); } - public MetricsConfigurationImpl(String registryName, VertxMetricsConfiguration vertxConfiguration) + public MetricsConfigurationImpl(String registryName, + VertxMetricsConfiguration vertxConfiguration, + List<MetricsFilteringConfiguration> includeConfigurations, + List<MetricsFilteringConfiguration> excludeConfigurations) { this.registryName = registryName; this.vertxConfiguration = vertxConfiguration; + this.includeConfigurations = includeConfigurations; + this.excludeConfigurations = excludeConfigurations; } /** @@ -64,4 +78,22 @@ public class MetricsConfigurationImpl implements MetricsConfiguration { return vertxConfiguration; } + + /** + * {@inheritDoc} + */ + @Override + public List<MetricsFilteringConfiguration> includeConfigurations() + { + return includeConfigurations; + } + + /** + * {@inheritDoc} + */ + @Override + public List<MetricsFilteringConfiguration> excludeConfigurations() + { + return excludeConfigurations; + } } diff --git a/src/main/java/org/apache/cassandra/sidecar/config/yaml/MetricsFilteringConfigurationImpl.java b/src/main/java/org/apache/cassandra/sidecar/config/yaml/MetricsFilteringConfigurationImpl.java new file mode 100644 index 00000000..9024d6f8 --- /dev/null +++ b/src/main/java/org/apache/cassandra/sidecar/config/yaml/MetricsFilteringConfigurationImpl.java @@ -0,0 +1,82 @@ +/* + * 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.cassandra.sidecar.config.yaml; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.cassandra.sidecar.config.MetricsFilteringConfiguration; + +/** + * Holds configuration needed for filtering out metrics + */ +public class MetricsFilteringConfigurationImpl implements MetricsFilteringConfiguration +{ + public static final String REGEX_TYPE = "regex"; + public static final String EQUALS_TYPE = "equals"; + public static final String DEFAULT_TYPE = REGEX_TYPE; + public static final String DEFAULT_VALUE = ".*"; // include all metrics + private String type; + @JsonProperty("value") + private final String value; + + public MetricsFilteringConfigurationImpl() + { + this(DEFAULT_TYPE, DEFAULT_VALUE); + } + + public MetricsFilteringConfigurationImpl(String type, String value) + { + this.type = type; + verifyType(type); + this.value = value; + } + + private void verifyType(String type) + { + if (REGEX_TYPE.equalsIgnoreCase(type) || EQUALS_TYPE.equalsIgnoreCase(type)) + { + return; + } + throw new IllegalArgumentException(type + " passed for metric filtering is not recognized. Expected types are " + + REGEX_TYPE + " or " + EQUALS_TYPE); + } + + /** + * {@inheritDoc} + */ + @Override + public String type() + { + return type; + } + + @JsonProperty(value = "type") + public void setType(String type) + { + verifyType(type); + this.type = type; + } + + /** + * {@inheritDoc} + */ + public String value() + { + return value; + } +} diff --git a/src/main/java/org/apache/cassandra/sidecar/metrics/FilteringMetricRegistry.java b/src/main/java/org/apache/cassandra/sidecar/metrics/FilteringMetricRegistry.java new file mode 100644 index 00000000..ad08b58b --- /dev/null +++ b/src/main/java/org/apache/cassandra/sidecar/metrics/FilteringMetricRegistry.java @@ -0,0 +1,225 @@ +/* + * 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.cassandra.sidecar.metrics; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; + +import com.codahale.metrics.Counter; +import com.codahale.metrics.Gauge; +import com.codahale.metrics.Histogram; +import com.codahale.metrics.Meter; +import com.codahale.metrics.Metric; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.NoopMetricRegistry; +import com.codahale.metrics.Timer; + +/** + * Allows filtering of metrics based on configured allow list. Metrics are filtered out before registering them. + */ +public class FilteringMetricRegistry extends MetricRegistry +{ + private static final NoopMetricRegistry NO_OP_METRIC_REGISTRY = new NoopMetricRegistry(); // supplies no-op metrics + private final Predicate<String> isAllowed; + private final Map<String, Metric> excludedMetrics = new ConcurrentHashMap<>(); + + public FilteringMetricRegistry(Predicate<String> isAllowedPredicate) + { + this.isAllowed = new CachedPredicate(isAllowedPredicate); + } + + @Override + public Counter counter(String name) + { + if (isAllowed.test(name)) + { + return super.counter(name); + } + return typeChecked(excludedMetrics.computeIfAbsent(name, NO_OP_METRIC_REGISTRY::counter), Counter.class); + } + + @Override + public Counter counter(String name, MetricSupplier<Counter> supplier) + { + if (isAllowed.test(name)) + { + return super.counter(name, supplier); + } + return typeChecked(excludedMetrics.computeIfAbsent(name, NO_OP_METRIC_REGISTRY::counter), Counter.class); + } + + @Override + public Histogram histogram(String name) + { + if (isAllowed.test(name)) + { + return super.histogram(name); + } + return typeChecked(excludedMetrics.computeIfAbsent(name, NO_OP_METRIC_REGISTRY::histogram), Histogram.class); + } + + @Override + public Histogram histogram(String name, MetricSupplier<Histogram> supplier) + { + if (isAllowed.test(name)) + { + return super.histogram(name, supplier); + } + return typeChecked(excludedMetrics.computeIfAbsent(name, NO_OP_METRIC_REGISTRY::histogram), Histogram.class); + } + + @Override + public Meter meter(String name) + { + if (isAllowed.test(name)) + { + return super.meter(name); + } + return typeChecked(excludedMetrics.computeIfAbsent(name, NO_OP_METRIC_REGISTRY::meter), Meter.class); + } + + @Override + public Meter meter(String name, MetricSupplier<Meter> supplier) + { + if (isAllowed.test(name)) + { + return super.meter(name, supplier); + } + return typeChecked(excludedMetrics.computeIfAbsent(name, NO_OP_METRIC_REGISTRY::meter), Meter.class); + } + + @Override + public Timer timer(String name) + { + if (isAllowed.test(name)) + { + return super.timer(name); + } + return typeChecked(excludedMetrics.computeIfAbsent(name, NO_OP_METRIC_REGISTRY::timer), Timer.class); + } + + @Override + public Timer timer(String name, MetricSupplier<Timer> supplier) + { + if (isAllowed.test(name)) + { + return super.timer(name, supplier); + } + return typeChecked(excludedMetrics.computeIfAbsent(name, NO_OP_METRIC_REGISTRY::timer), Timer.class); + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public <T extends Gauge> T gauge(String name) + { + if (isAllowed.test(name)) + { + return super.gauge(name); + } + return (T) typeChecked(excludedMetrics.computeIfAbsent(name, NO_OP_METRIC_REGISTRY::gauge), Gauge.class); + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public <T extends Gauge> T gauge(String name, MetricSupplier<T> supplier) + { + if (isAllowed.test(name)) + { + return super.gauge(name, supplier); + } + return (T) typeChecked(excludedMetrics.computeIfAbsent(name, k -> supplier.newMetric() /* unregistered metric */), Gauge.class); + } + + /** + * @return all the metrics including the allowed and disallowed metrics. This is to prevent re-registering of + * excluded metrics + */ + @Override + public Map<String, Metric> getMetrics() + { + Map<String, Metric> allMetrics = new HashMap<>(); + allMetrics.putAll(super.getMetrics()); + allMetrics.putAll(excludedMetrics); + return Collections.unmodifiableMap(allMetrics); + } + + /** + * @return metrics registered with {@code super.register()}. This will be useful for testing purposes to check + * what metrics are actually captured + */ + public Map<String, Metric> getIncludedMetrics() + { + return super.getMetrics(); + } + + /** + * Metric specific retrieve methods such as {@code counter(name)} retrieve a noop instance if metric is filtered. + * Prefer calling those over register method, register method returns an unregistered metric if the metric is + * filtered. In some cases Noop metric instance has a performance advantage. + */ + public <T extends Metric> T register(String name, T metric) throws IllegalArgumentException + { + if (metric == null) + { + throw new IllegalArgumentException("Metric can not be null"); + } + + // The metric is registered by calling the register() directly + // We need to test whether it is allowed first + if (isAllowed.test(name)) + { + return super.register(name, metric); + } + + return (T) typeChecked(excludedMetrics.computeIfAbsent(name, key -> metric), metric.getClass()); + } + + private <T extends Metric> T typeChecked(Metric metric, Class<T> type) + { + if (type.isInstance(metric)) + { + return (T) metric; + } + throw new IllegalArgumentException("Metric already present with type " + metric.getClass()); + } + + /** + * {@link CachedPredicate} remembers results of the {@link Predicate} it maintains. This is to avoid + * redundant calls to delegate predicate. + */ + static class CachedPredicate implements Predicate<String> + { + private final Predicate<String> delegate; + private final Map<String, Boolean> results = new ConcurrentHashMap<>(); + + CachedPredicate(Predicate<String> delegate) + { + this.delegate = delegate; + } + + @Override + public boolean test(String s) + { + return results.computeIfAbsent(s, t -> delegate.test(s)); + } + } +} diff --git a/src/main/java/org/apache/cassandra/sidecar/metrics/MetricFilter.java b/src/main/java/org/apache/cassandra/sidecar/metrics/MetricFilter.java new file mode 100644 index 00000000..7f4e5253 --- /dev/null +++ b/src/main/java/org/apache/cassandra/sidecar/metrics/MetricFilter.java @@ -0,0 +1,97 @@ +/* + * 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.cassandra.sidecar.metrics; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +import org.apache.cassandra.sidecar.config.MetricsFilteringConfiguration; + +import static org.apache.cassandra.sidecar.config.yaml.MetricsFilteringConfigurationImpl.EQUALS_TYPE; +import static org.apache.cassandra.sidecar.config.yaml.MetricsFilteringConfigurationImpl.REGEX_TYPE; + +/** + * Filter for deciding whether a metric should be captured + */ +public abstract class MetricFilter +{ + /** + * @return Boolean indicating whether the {@link MetricFilter} is satisfied for provided metric name. + */ + public abstract boolean matches(String name); + + public static List<MetricFilter> parse(List<MetricsFilteringConfiguration> filterConfigurations) + { + List<MetricFilter> filters = new ArrayList<>(); + for (MetricsFilteringConfiguration filterConfiguration : filterConfigurations) + { + if (filterConfiguration.type().equalsIgnoreCase(REGEX_TYPE)) + { + filters.add(new Regex(filterConfiguration.value())); + } + else if (filterConfiguration.type().equalsIgnoreCase(EQUALS_TYPE)) + { + filters.add(new Equals(filterConfiguration.value())); + } + } + return filters; + } + + /** + * Metric name based {@link MetricFilter} that checks if a metric name matches provided regex pattern + */ + public static class Regex extends MetricFilter + { + private final Pattern pattern; + + public Regex(String regex) + { + Objects.requireNonNull(regex); + this.pattern = Pattern.compile(regex); + } + + @Override + public boolean matches(String name) + { + return pattern.matcher(name).matches(); + } + } + + /** + * Metric name based {@link MetricFilter} that checks if a metric name exactly matches provided value + */ + public static class Equals extends MetricFilter + { + private final String value; + + public Equals(String value) + { + Objects.requireNonNull(value); + this.value = value; + } + + @Override + public boolean matches(String name) + { + return name.equals(value); + } + } +} diff --git a/src/main/java/org/apache/cassandra/sidecar/metrics/MetricRegistryFactory.java b/src/main/java/org/apache/cassandra/sidecar/metrics/MetricRegistryFactory.java new file mode 100644 index 00000000..a27dca41 --- /dev/null +++ b/src/main/java/org/apache/cassandra/sidecar/metrics/MetricRegistryFactory.java @@ -0,0 +1,103 @@ +/* + * 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.cassandra.sidecar.metrics; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.annotations.VisibleForTesting; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.SharedMetricRegistries; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import org.apache.cassandra.sidecar.config.SidecarConfiguration; + +/** + * Provider for getting {@link FilteringMetricRegistry} based on provided metrics configuration + */ +@Singleton +public class MetricRegistryFactory +{ + private final String globalRegistryName; + private final List<MetricFilter> inclusions; + private final List<MetricFilter> exclusions; + + @Inject + public MetricRegistryFactory(SidecarConfiguration sidecarConfiguration) + { + this(sidecarConfiguration.metricsConfiguration().registryName(), + MetricFilter.parse(sidecarConfiguration.metricsConfiguration().includeConfigurations()), + MetricFilter.parse(sidecarConfiguration.metricsConfiguration().excludeConfigurations())); + } + + @VisibleForTesting + public MetricRegistryFactory(String globalRegistryName, List<MetricFilter> inclusions, List<MetricFilter> exclusions) + { + this.globalRegistryName = globalRegistryName; + this.inclusions = new ArrayList<>(inclusions); + this.exclusions = new ArrayList<>(exclusions); + } + + public MetricRegistry getOrCreate() + { + return getOrCreate(globalRegistryName); + } + + public MetricRegistry getOrCreate(int cassInstanceId) + { + String instanceRegistryName = globalRegistryName + "_" + cassInstanceId; + return getOrCreate(instanceRegistryName); + } + + /** + * Provides a {@link FilteringMetricRegistry} with given name and provided filters in configuration. + * @param name registry name + * @return a {@link MetricRegistry} that can filter out metrics + */ + public MetricRegistry getOrCreate(String name) + { + // the metric registry already exists + if (SharedMetricRegistries.names().contains(name)) + { + return SharedMetricRegistries.getOrCreate(name); + } + + FilteringMetricRegistry metricRegistry = new FilteringMetricRegistry(this::isAllowed); + SharedMetricRegistries.add(name, metricRegistry); + return SharedMetricRegistries.getOrCreate(name); + } + + /** + * Check if the metric is allowed to register + * The evaluation order is inclusions first, then exclusions. In other words, + * a metric name is allowed if it is in the inclusions, but not in the exclusions. + * <p> + * Note that an empty inclusions means including all + * + * @param name metric name + * @return true if allowed; false otherwise + */ + private boolean isAllowed(String name) + { + boolean included = inclusions.isEmpty() || inclusions.stream().anyMatch(filter -> filter.matches(name)); + boolean excluded = exclusions.stream().anyMatch(filter -> filter.matches(name)); + return included && !excluded; + } +} diff --git a/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java b/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java index 9db7d95a..448bcbb6 100644 --- a/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java +++ b/src/main/java/org/apache/cassandra/sidecar/server/MainModule.java @@ -30,7 +30,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.codahale.metrics.MetricRegistry; -import com.codahale.metrics.SharedMetricRegistries; import com.datastax.driver.core.NettyOptions; import com.google.inject.AbstractModule; import com.google.inject.Provides; @@ -76,6 +75,7 @@ import org.apache.cassandra.sidecar.db.schema.RestoreSlicesSchema; import org.apache.cassandra.sidecar.db.schema.SidecarInternalKeyspace; import org.apache.cassandra.sidecar.db.schema.SidecarSchema; import org.apache.cassandra.sidecar.logging.SidecarLoggerHandler; +import org.apache.cassandra.sidecar.metrics.MetricRegistryFactory; import org.apache.cassandra.sidecar.routes.CassandraHealthHandler; import org.apache.cassandra.sidecar.routes.DiskSpaceProtectionHandler; import org.apache.cassandra.sidecar.routes.FileStreamHandler; @@ -142,14 +142,14 @@ public class MainModule extends AbstractModule @Provides @Singleton - public Vertx vertx(SidecarConfiguration sidecarConfiguration) + public Vertx vertx(SidecarConfiguration sidecarConfiguration, MetricRegistryFactory metricRegistryFactory) { VertxMetricsConfiguration metricsConfig = sidecarConfiguration.metricsConfiguration().vertxConfiguration(); DropwizardMetricsOptions dropwizardMetricsOptions = new DropwizardMetricsOptions().setEnabled(metricsConfig.enabled()) .setJmxEnabled(metricsConfig.exposeViaJMX()) .setJmxDomain(metricsConfig.jmxDomainName()) - .setRegistryName(sidecarConfiguration.metricsConfiguration().registryName()); + .setMetricRegistry(metricRegistryFactory.getOrCreate()); for (String regex : metricsConfig.monitoredServerRouteRegexes()) { dropwizardMetricsOptions.addMonitoredHttpServerRoute(new Match().setType(MatchType.REGEX).setValue(regex)); @@ -370,7 +370,8 @@ public class MainModule extends AbstractModule SidecarVersionProvider sidecarVersionProvider, DnsResolver dnsResolver, CQLSessionProvider cqlSessionProvider, - DriverUtils driverUtils) + DriverUtils driverUtils, + MetricRegistryFactory registryProvider) { List<InstanceMetadata> instanceMetadataList = configuration.cassandraInstances() @@ -384,7 +385,7 @@ public class MainModule extends AbstractModule jmxConfiguration, cqlSessionProvider, driverUtils, - configuration.metricsConfiguration().registryName()); + registryProvider); }) .collect(Collectors.toList()); @@ -525,9 +526,9 @@ public class MainModule extends AbstractModule @Provides @Singleton - public MetricRegistry globalMetricRegistry(SidecarConfiguration sidecarConfiguration) + public MetricRegistry globalMetricRegistry(MetricRegistryFactory registryProvider) { - return SharedMetricRegistries.getOrCreate(sidecarConfiguration.metricsConfiguration().registryName()); + return registryProvider.getOrCreate(); } /** @@ -559,7 +560,7 @@ public class MainModule extends AbstractModule * @param sidecarVersion the version of the Sidecar from the current binary * @param jmxConfiguration the configuration for the JMX Client * @param session the CQL Session provider - * @param globalRegistryName global registry name used for tracking Sidecar metrics + * @param registryFactory factory for creating cassandra instance specific registry * @return the build instance metadata object */ private static InstanceMetadata buildInstanceMetadata(Vertx vertx, @@ -569,7 +570,7 @@ public class MainModule extends AbstractModule JmxConfiguration jmxConfiguration, CQLSessionProvider session, DriverUtils driverUtils, - String globalRegistryName) + MetricRegistryFactory registryFactory) { String host = cassandraInstance.host(); int port = cassandraInstance.port(); @@ -592,6 +593,7 @@ public class MainModule extends AbstractModule sidecarVersion, host, port); + return InstanceMetadataImpl.builder() .id(cassandraInstance.id()) .host(host) @@ -599,7 +601,7 @@ public class MainModule extends AbstractModule .dataDirs(cassandraInstance.dataDirs()) .stagingDir(cassandraInstance.stagingDir()) .delegate(delegate) - .globalMetricRegistryName(globalRegistryName) + .metricRegistry(registryFactory.getOrCreate(cassandraInstance.id())) .build(); } } diff --git a/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java b/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java index e101e391..e3ce1121 100644 --- a/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java +++ b/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java @@ -23,6 +23,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -46,6 +47,7 @@ import org.apache.cassandra.sidecar.common.CQLSessionProvider; import org.apache.cassandra.sidecar.common.JmxClient; import org.apache.cassandra.sidecar.common.dns.DnsResolver; import org.apache.cassandra.sidecar.common.utils.DriverUtils; +import org.apache.cassandra.sidecar.metrics.MetricRegistryFactory; import org.apache.cassandra.sidecar.utils.CassandraVersionProvider; import org.apache.cassandra.sidecar.utils.SimpleCassandraVersion; import org.apache.cassandra.testing.AbstractCassandraTestContext; @@ -59,6 +61,9 @@ import static org.assertj.core.api.Assertions.assertThat; public class CassandraSidecarTestContext implements AutoCloseable { public final SimpleCassandraVersion version; + private final MetricRegistryFactory metricRegistryProvider = new MetricRegistryFactory("cassandra_sidecar", + Collections.emptyList(), + Collections.emptyList()); private final CassandraVersionProvider versionProvider; private final DnsResolver dnsResolver; private final AbstractCassandraTestContext abstractCassandraTestContext; @@ -257,7 +262,7 @@ public class CassandraSidecarTestContext implements AutoCloseable .dataDirs(Arrays.asList(dataDirectories)) .stagingDir(stagingDir) .delegate(delegate) - .globalMetricRegistryName("cassandra_sidecar") + .metricRegistry(metricRegistryProvider.getOrCreate(i + 1)) .build()); } return new InstancesConfigImpl(metadata, dnsResolver); diff --git a/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java b/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java index 3498d970..221132b7 100644 --- a/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java +++ b/src/test/java/org/apache/cassandra/sidecar/config/SidecarConfigurationTest.java @@ -29,6 +29,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; import com.fasterxml.jackson.databind.JsonMappingException; +import org.apache.cassandra.sidecar.config.yaml.MetricsFilteringConfigurationImpl; import org.apache.cassandra.sidecar.config.yaml.SidecarConfigurationImpl; import org.apache.cassandra.sidecar.config.yaml.VertxMetricsConfigurationImpl; import org.assertj.core.api.Condition; @@ -38,7 +39,8 @@ import static org.apache.cassandra.sidecar.common.ApiEndpointsV1.KEYSPACE_SCHEMA import static org.apache.cassandra.sidecar.common.ApiEndpointsV1.RESTORE_JOB_SLICES_ROUTE; import static org.apache.cassandra.sidecar.common.ApiEndpointsV1.TIME_SKEW_ROUTE; import static org.apache.cassandra.sidecar.common.ResourceUtils.writeResourceToPath; -import static org.apache.cassandra.sidecar.config.yaml.MetricsConfigurationImpl.DEFAULT_DROPWIZARD_REGISTRY_NAME; +import static org.apache.cassandra.sidecar.config.yaml.MetricsFilteringConfigurationImpl.EQUALS_TYPE; +import static org.apache.cassandra.sidecar.config.yaml.MetricsFilteringConfigurationImpl.REGEX_TYPE; import static org.apache.cassandra.sidecar.config.yaml.VertxMetricsConfigurationImpl.DEFAULT_JMX_DOMAIN_NAME; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatExceptionOfType; @@ -221,14 +223,40 @@ class SidecarConfigurationTest SidecarConfiguration config = SidecarConfigurationImpl.readYamlConfiguration(yamlPath); MetricsConfiguration configuration = config.metricsConfiguration(); - assertThat(configuration.registryName()).isEqualTo(DEFAULT_DROPWIZARD_REGISTRY_NAME); + assertThat(configuration.registryName()).isEqualTo("cassandra_sidecar_metrics"); VertxMetricsConfiguration vertxMetricsConfiguration = configuration.vertxConfiguration(); assertThat(vertxMetricsConfiguration.enabled()).isTrue(); assertThat(vertxMetricsConfiguration.exposeViaJMX()).isFalse(); assertThat(vertxMetricsConfiguration.jmxDomainName()).isEqualTo(DEFAULT_JMX_DOMAIN_NAME); - assertThat(vertxMetricsConfiguration.monitoredServerRouteRegexes().size()).isEqualTo(2); - assertThat(vertxMetricsConfiguration.monitoredServerRouteRegexes().get(0)).isEqualTo("/api/v1/keyspaces/.*"); - assertThat(vertxMetricsConfiguration.monitoredServerRouteRegexes().get(1)).isEqualTo("/api/v1/cassandra/.*"); + List<MetricsFilteringConfiguration> includeConfigurations = configuration.includeConfigurations(); + List<MetricsFilteringConfiguration> excludeConfigurations = configuration.excludeConfigurations(); + assertThat(includeConfigurations.size()).isEqualTo(1); + assertThat(excludeConfigurations.size()).isEqualTo(2); + assertThat(includeConfigurations.get(0).type()).isEqualTo("regex"); + assertThat(includeConfigurations.get(0).value()).isEqualTo(".*"); + if (excludeConfigurations.get(0).type().equals("regex")) + { + assertThat(excludeConfigurations.get(0).value()).isEqualTo("vertx.eventbus.*"); + assertThat(excludeConfigurations.get(1).type()).isEqualTo("equals"); + assertThat(excludeConfigurations.get(1).value()).isEqualTo("instances_up"); + } + else + { + assertThat(excludeConfigurations.get(1).value()).isEqualTo("vertx.eventbus.*"); + assertThat(excludeConfigurations.get(1).type()).isEqualTo("regex"); + assertThat(excludeConfigurations.get(0).value()).isEqualTo("instances_up"); + } + } + + @Test + void testInvalidMetricOptions() + { + Path yamlPath = yaml("config/sidecar_invalid_metrics.yaml"); + assertThatExceptionOfType(JsonMappingException.class) + .isThrownBy(() -> SidecarConfigurationImpl.readYamlConfiguration(yamlPath)) + .withRootCauseInstanceOf(IllegalArgumentException.class) + .withMessageContaining("contains passed for metric filtering is not recognized. Expected types are " + + REGEX_TYPE + " or " + EQUALS_TYPE); } @Test @@ -243,6 +271,16 @@ class SidecarConfigurationTest assertThat(pattern.matcher(RESTORE_JOB_SLICES_ROUTE)).matches(); } + @Test + void testMetricsAllowedWithDefaultRegexFilter() + { + Pattern pattern = Pattern.compile(MetricsFilteringConfigurationImpl.DEFAULT_VALUE); + assertThat(pattern.matcher("vertx.http.servers.localhost:0.responses-5xx")).matches(); + assertThat(pattern.matcher("sidecar.schema.cassandra_instances_up")).matches(); + assertThat(pattern.matcher("vertx.eventbus.messages.bytes-read")).matches(); + assertThat(pattern.matcher("throttled_429")).matches(); + } + void validateSingleInstanceSidecarConfiguration(SidecarConfiguration config) { assertThat(config.cassandraInstances()).isNotNull().hasSize(1); @@ -431,8 +469,11 @@ class SidecarConfigurationTest void validateMetricsConfiguration(MetricsConfiguration config) { + assertThat(config.registryName()).isNotEmpty(); assertThat(config.vertxConfiguration()).isNotNull(); assertThat(config.vertxConfiguration().monitoredServerRouteRegexes()).isNotNull(); + assertThat(config.includeConfigurations()).isNotNull(); + assertThat(config.excludeConfigurations()).isNotNull(); } private Path yaml(String resourceName) diff --git a/src/test/java/org/apache/cassandra/sidecar/metrics/FilteringMetricRegistryTest.java b/src/test/java/org/apache/cassandra/sidecar/metrics/FilteringMetricRegistryTest.java new file mode 100644 index 00000000..bfeb8d9c --- /dev/null +++ b/src/test/java/org/apache/cassandra/sidecar/metrics/FilteringMetricRegistryTest.java @@ -0,0 +1,213 @@ +/* + * 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.cassandra.sidecar.metrics; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.UUID; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; + +import com.codahale.metrics.DefaultSettableGauge; +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.NoopMetricRegistry; +import com.google.inject.Guice; +import com.google.inject.Injector; +import io.vertx.core.Vertx; +import io.vertx.ext.dropwizard.ThroughputMeter; +import io.vertx.junit5.Checkpoint; +import io.vertx.junit5.VertxExtension; +import io.vertx.junit5.VertxTestContext; +import org.apache.cassandra.sidecar.server.MainModule; +import org.apache.cassandra.sidecar.server.Server; +import org.apache.cassandra.sidecar.server.SidecarServerEvents; + +import static org.apache.cassandra.sidecar.common.ResourceUtils.writeResourceToPath; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Test for filtering of metrics + */ +@ExtendWith(VertxExtension.class) +public class FilteringMetricRegistryTest +{ + private static final MetricRegistry NO_OP_METRIC_REGISTRY = new NoopMetricRegistry(); + @TempDir + private Path confPath; + + @Test + void testNoopInstanceRetrieved() + { + MetricFilter.Regex testFilter = new MetricFilter.Regex("testMetric.*"); + MetricRegistryFactory registryFactory = new MetricRegistryFactory("cassandra_sidecar_" + UUID.randomUUID(), + Collections.emptyList(), + Collections.singletonList(testFilter)); + FilteringMetricRegistry metricRegistry = (FilteringMetricRegistry) registryFactory.getOrCreate(); + + assertThat(metricRegistry.timer("testMetricTimer")).isSameAs(NO_OP_METRIC_REGISTRY.timer("any")); + assertThat(metricRegistry.meter("testMetricMeter")).isSameAs(NO_OP_METRIC_REGISTRY.meter("any")); + assertThat(metricRegistry.counter("testMetricCounter")).isSameAs(NO_OP_METRIC_REGISTRY.counter("any")); + assertThat(metricRegistry.histogram("testMetricHistogram")).isSameAs(NO_OP_METRIC_REGISTRY.histogram("any")); + + metricRegistry.register("testMetricThroughputMeter", new ThroughputMeter()); + assertThat(metricRegistry.getIncludedMetrics()).doesNotContainKey("testMetricThroughputMeter"); + } + + @Test + void testDuplicateMetricsNotAllowed() + { + MetricRegistry metricRegistry = new MetricRegistry(); + assertThat(metricRegistry.timer("testMetric")).isNotNull(); + assertThatThrownBy(() -> metricRegistry.meter("testMetric")) + .isInstanceOf(IllegalArgumentException.class); + + MetricRegistryFactory registryFactory = new MetricRegistryFactory("cassandra_sidecar_" + UUID.randomUUID(), + Collections.emptyList(), + Collections.emptyList()); + FilteringMetricRegistry filteringMetricRegistry = (FilteringMetricRegistry) registryFactory.getOrCreate(); + + filteringMetricRegistry.timer("testMetric"); + assertThatThrownBy(() -> filteringMetricRegistry.meter("testMetric")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void testGaugeMetricExclusion() + { + MetricFilter.Regex testFilter = new MetricFilter.Regex("testMetric.*"); + MetricRegistryFactory registryFactory = new MetricRegistryFactory("cassandra_sidecar_" + UUID.randomUUID(), + Collections.emptyList(), + Collections.singletonList(testFilter)); + FilteringMetricRegistry metricRegistry = (FilteringMetricRegistry) registryFactory.getOrCreate(); + + assertThat(metricRegistry.gauge("testMetricGauge", () -> new DefaultSettableGauge<>(0L))) + .isInstanceOf(DefaultSettableGauge.class); + assertThat(metricRegistry.getIncludedMetrics()).doesNotContainKey("testMetricGauge"); + + metricRegistry.register("testMetricDefaultSettableGaugeLong", new DefaultSettableGauge<>(0L)); + assertThat(metricRegistry.getIncludedMetrics()).doesNotContainKey("testMetricDefaultSettableGaugeLong"); + + metricRegistry.register("testMetricDefaultSettableGaugeDouble", new DefaultSettableGauge<>(0d)); + assertThat(metricRegistry.getIncludedMetrics()).doesNotContainKey("testMetricDefaultSettableGaugeDouble"); + } + + @Test + void testOneMatchingFilter() + { + MetricFilter.Equals exactFilter = new MetricFilter.Equals("sidecar.metric.exact"); + MetricFilter.Regex regexFilter = new MetricFilter.Regex("vertx.*"); + MetricRegistryFactory registryFactory = new MetricRegistryFactory("cassandra_sidecar_" + UUID.randomUUID(), + Collections.singletonList(exactFilter), + Collections.singletonList(regexFilter)); + FilteringMetricRegistry metricRegistry = (FilteringMetricRegistry) registryFactory.getOrCreate(); + + metricRegistry.meter("sidecar.metric.exact"); + assertThat(metricRegistry.getIncludedMetrics()).containsKey("sidecar.metric.exact"); + } + + @Test + void testMultipleMatchingFilter() + { + MetricFilter.Equals exactFilter = new MetricFilter.Equals("sidecar.metric.exact"); + MetricFilter.Regex regexFilter = new MetricFilter.Regex("sidecar.*"); + MetricRegistryFactory registryFactory = new MetricRegistryFactory("cassandra_sidecar_" + UUID.randomUUID(), + Arrays.asList(exactFilter, regexFilter), + Collections.emptyList()); + FilteringMetricRegistry metricRegistry = (FilteringMetricRegistry) registryFactory.getOrCreate(); + + metricRegistry.meter("sidecar.metric.exact"); + assertThat(metricRegistry.getIncludedMetrics()).containsKey("sidecar.metric.exact"); + } + + @Test + void testExcludingEqualsMetricFilter() + { + MetricFilter.Equals exactFilter = new MetricFilter.Equals("sidecar.metric.exact"); + MetricRegistryFactory registryFactory = new MetricRegistryFactory("cassandra_sidecar_" + UUID.randomUUID(), + Collections.emptyList(), + Collections.singletonList(exactFilter)); + FilteringMetricRegistry metricRegistry = (FilteringMetricRegistry) registryFactory.getOrCreate(); + + metricRegistry.meter("sidecar.metric.exact"); + assertThat(metricRegistry.getIncludedMetrics()).doesNotContainKey("sidecar.metric.exact"); + } + + @Test + void testExcludingRegexMetricFilter() + { + MetricFilter.Regex vertxFilter = new MetricFilter.Regex("vertx.*"); + MetricFilter.Regex sidecarFilter = new MetricFilter.Regex("sidecar.*"); + MetricRegistryFactory registryProvider = new MetricRegistryFactory("cassandra_sidecar_" + UUID.randomUUID(), + Collections.singletonList(sidecarFilter), + Collections.singletonList(vertxFilter)); + FilteringMetricRegistry metricRegistry = (FilteringMetricRegistry) registryProvider.getOrCreate(); + + metricRegistry.meter("sidecar.metric.exact"); + assertThat(metricRegistry.getMetrics()).containsKey("sidecar.metric.exact"); + metricRegistry.timer("vertx.eventbus.message_transfer_time"); + assertThat(metricRegistry.getIncludedMetrics()).doesNotContainKey("vertx.eventbus.message_transfer_time"); + } + + @Test + void testMultipleMatchingFilterWithOneExclude() + { + MetricFilter.Equals exactFilter = new MetricFilter.Equals("sidecar.metric.exact"); + MetricFilter.Regex regexFilter = new MetricFilter.Regex("sidecar.*"); + MetricRegistryFactory registryFactory = new MetricRegistryFactory("cassandra_sidecar_" + UUID.randomUUID(), + Collections.singletonList(regexFilter), + Collections.singletonList(exactFilter)); + FilteringMetricRegistry metricRegistry = (FilteringMetricRegistry) registryFactory.getOrCreate(); + + metricRegistry.meter("sidecar.metric.exact"); + assertThat(metricRegistry.getIncludedMetrics()).doesNotContainKey("sidecar.metric.exact"); + } + + @Test + void testExclusionsWithServer(VertxTestContext context) + { + ClassLoader classLoader = FilteringMetricRegistryTest.class.getClassLoader(); + Path yamlPath = writeResourceToPath(classLoader, confPath, "config/sidecar_metrics.yaml"); + Injector injector = Guice.createInjector(new MainModule(yamlPath)); + Server server = injector.getInstance(Server.class); + Vertx vertx = injector.getInstance(Vertx.class); + + Checkpoint serverStarted = context.checkpoint(); + Checkpoint waitUntilCheck = context.checkpoint(); + + vertx.eventBus().localConsumer(SidecarServerEvents.ON_SERVER_START.address(), message -> serverStarted.flag()); + + server.start() + .onFailure(context::failNow) + .onSuccess(v -> { + MetricRegistryFactory registryFactory = injector.getInstance(MetricRegistryFactory.class); + Pattern excludedPattern = Pattern.compile("vertx.eventbus.*"); + FilteringMetricRegistry globalRegistry = (FilteringMetricRegistry) registryFactory.getOrCreate(); + assertThat(globalRegistry.getIncludedMetrics().size()).isGreaterThanOrEqualTo(1); + assertThat(globalRegistry.getIncludedMetrics().keySet().stream()) + .noneMatch(key -> excludedPattern.matcher(key).matches()); + waitUntilCheck.flag(); + context.completeNow(); + }); + } +} diff --git a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java index 744daa2e..85cba26e 100644 --- a/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java +++ b/src/test/java/org/apache/cassandra/sidecar/snapshots/SnapshotUtils.java @@ -40,6 +40,8 @@ import org.apache.cassandra.sidecar.common.JmxClient; import org.apache.cassandra.sidecar.common.MockCassandraFactory; import org.apache.cassandra.sidecar.common.dns.DnsResolver; import org.apache.cassandra.sidecar.common.utils.DriverUtils; +import org.apache.cassandra.sidecar.metrics.MetricFilter; +import org.apache.cassandra.sidecar.metrics.MetricRegistryFactory; import org.apache.cassandra.sidecar.utils.CassandraVersionProvider; import static org.assertj.core.api.Assertions.assertThat; @@ -51,6 +53,10 @@ import static org.mockito.Mockito.mock; public class SnapshotUtils { public static final String STAGING_DIR = "staging"; + public static final List<MetricFilter> INCLUDE_ALL = Collections.singletonList(new MetricFilter.Regex(".*")); + public static final MetricRegistryFactory METRIC_REGISTRY_PROVIDER = new MetricRegistryFactory("cassandra_sidecar", + INCLUDE_ALL, + Collections.emptyList()); public static String makeStagingDir(String rootPath) { @@ -124,7 +130,7 @@ public class SnapshotUtils .dataDirs(Collections.singletonList(rootPath + "/d1")) .stagingDir(stagingDir) .delegate(delegate) - .globalMetricRegistryName("cassandra_sidecar") + .metricRegistry(METRIC_REGISTRY_PROVIDER.getOrCreate(1)) .build(); InstanceMetadataImpl localhost2 = InstanceMetadataImpl.builder() .id(2) @@ -133,7 +139,7 @@ public class SnapshotUtils .dataDirs(Collections.singletonList(rootPath + "/d2")) .stagingDir(stagingDir) .delegate(delegate) - .globalMetricRegistryName("cassandra_sidecar") + .metricRegistry(METRIC_REGISTRY_PROVIDER.getOrCreate(2)) .build(); List<InstanceMetadata> instanceMetas = Arrays.asList(localhost, localhost2); return new InstancesConfigImpl(instanceMetas, DnsResolver.DEFAULT); diff --git a/src/test/resources/config/sidecar_invalid_metrics.yaml b/src/test/resources/config/sidecar_invalid_metrics.yaml new file mode 100644 index 00000000..32d377c2 --- /dev/null +++ b/src/test/resources/config/sidecar_invalid_metrics.yaml @@ -0,0 +1,47 @@ +# +# 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. +# + +# +# Cassandra SideCar configuration file +# +cassandra: + host: localhost + port: 9042 + data_dirs: /cassandra/d1/data, /cassandra/d2/data + jmx_host: 127.0.0.1 + jmx_port: 7199 + jmx_role: controlRole + jmx_role_password: controlPassword + jmx_ssl_enabled: true + +sidecar: + host: 0.0.0.0 + port: 1234 + request_idle_timeout_millis: 500000 # this field expects integer value + request_timeout_millis: 1200000 + allowable_time_skew_in_minutes: 1 + +metrics: + registry_name: cassandra_sidecar_metrics + vertx: + enabled: true + expose_via_jmx: false + jmx_domain_name: sidecar.vertx.jmx_domain + exclude: + - type: "contains" + value: "vertx" diff --git a/src/test/resources/config/sidecar_metrics.yaml b/src/test/resources/config/sidecar_metrics.yaml index 689f157e..2cc734db 100644 --- a/src/test/resources/config/sidecar_metrics.yaml +++ b/src/test/resources/config/sidecar_metrics.yaml @@ -1,36 +1,39 @@ +# +# 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. +# + # # Cassandra SideCar configuration file # -cassandra: +cassandra_instances: host: localhost port: 9042 - data_dirs: /cassandra/d1/data, /cassandra/d2/data + username: cassandra + password: cassandra + data_dirs: + - /ccm/test/node1/data0 + - /ccm/test/node1/data1 + staging_dir: /ccm/test/node1/sstable-staging jmx_host: 127.0.0.1 jmx_port: 7199 jmx_role: controlRole jmx_role_password: controlPassword jmx_ssl_enabled: true -cassandra_instances: - - id: 1 - host: localhost1 - port: 9042 - data_dirs: /cassandra/d1/data, /cassandra/d2/data - jmx_host: 127.0.0.1 - jmx_port: 7100 - jmx_role: controlRole - jmx_role_password: controlPassword - jmx_ssl_enabled: true - - id: 2 - host: localhost2 - port: 9042 - data_dirs: /cassandra/d3/data, /cassandra/d4/data - jmx_host: 127.0.0.1 - jmx_port: 7200 - jmx_role: controlRole - jmx_role_password: controlPassword - jmx_ssl_enabled: true - sidecar: host: 0.0.0.0 port: 9043 @@ -40,9 +43,9 @@ sidecar: timeout_sec: 10 allowable_time_skew_in_minutes: 60 jmx: - connection: - max_retries: 40 - retry_delay_millis: 2000 + max_retries: 42 + retry_delay_millis: 1234 + # # Enable SSL configuration (Disabled by default) # @@ -56,14 +59,34 @@ sidecar: # - password: password metrics: - registry_name: cassandra_sidecar + registry_name: cassandra_sidecar_metrics vertx: enabled: true expose_via_jmx: false jmx_domain_name: sidecar.vertx.jmx_domain - monitored_server_route_regexes: # regex list to match server routes - - /api/v1/keyspaces/.* - - /api/v1/cassandra/.* + include: + - type: "regex" + value: ".*" + exclude: + - type: "regex" + value: "vertx.eventbus.*" + - type: "equals" + value: "instances_up" healthcheck: - poll_freq_millis: 30000 \ No newline at end of file + initial_delay_millis: 100 + poll_freq_millis: 30000 + +cassandra_input_validation: + forbidden_keyspaces: + - system_schema + - system_traces + - system_distributed + - system + - system_auth + - system_views + - system_virtual_schema + allowed_chars_for_directory: "[a-zA-Z][a-zA-Z0-9_]{0,47}" + allowed_chars_for_quoted_name: "[a-zA-Z_0-9]{1,48}" + allowed_chars_for_component_name: "[a-zA-Z0-9_-]+(.db|.cql|.json|.crc32|TOC.txt)" + allowed_chars_for_restricted_component_name: "[a-zA-Z0-9_-]+(.db|TOC.txt)" \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cassandra.apache.org For additional commands, e-mail: commits-h...@cassandra.apache.org