blob: ad08b58b4a4a426cba555fd08cd18976c3b2fd8a [file] [log] [blame]
/*
* 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));
}
}
}