org.apache.solr.metrics.MetricSuppliers Maven / Gradle / Ivy
Show all versions of solr-core Show documentation
/*
* 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.solr.metrics;
import com.codahale.metrics.Clock;
import com.codahale.metrics.Counter;
import com.codahale.metrics.ExponentiallyDecayingReservoir;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Histogram;
import com.codahale.metrics.Meter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Reservoir;
import com.codahale.metrics.SlidingTimeWindowReservoir;
import com.codahale.metrics.SlidingWindowReservoir;
import com.codahale.metrics.Timer;
import com.codahale.metrics.UniformReservoir;
import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.apache.solr.core.MetricsConfig;
import org.apache.solr.core.PluginInfo;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.util.SolrPluginUtils;
import org.apache.solr.util.plugin.PluginInfoInitialized;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Helper class for constructing instances of {@link
* com.codahale.metrics.MetricRegistry.MetricSupplier} based on plugin configuration. This allows us
* to customize eg. {@link com.codahale.metrics.Reservoir} implementations and parameters for timers
* and histograms.
*
* Custom supplier implementations must provide a zero-args constructor, and may optionally
* implement {@link org.apache.solr.util.plugin.PluginInfoInitialized} interface for configuration -
* if they don't then {@link org.apache.solr.util.SolrPluginUtils#invokeSetters(Object, Iterable,
* boolean)} will be used for initialization.
*/
public class MetricSuppliers {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/** Default {@link Counter} supplier. No configuration available. */
public static final class DefaultCounterSupplier
implements MetricRegistry.MetricSupplier {
@Override
public Counter newMetric() {
return new Counter();
}
}
// back-compat implementation, no longer present in metrics-4
private static final class CpuTimeClock extends Clock {
private static final ThreadMXBean THREAD_MX_BEAN = ManagementFactory.getThreadMXBean();
@Override
public long getTick() {
return THREAD_MX_BEAN.getCurrentThreadCpuTime();
}
}
private static final Clock CPU_CLOCK = new CpuTimeClock();
private static final Clock USER_CLOCK = new Clock.UserTimeClock();
/** Clock type parameter. */
public static final String CLOCK = "clock";
/** User-time clock. */
public static final String CLOCK_USER = "user";
/** CPU-time clock. */
public static final String CLOCK_CPU = "cpu";
/**
* Default {@link Meter} supplier. The following configuration is available, either as attribute
* or initArgs:
*
*
* - clock - (string) can be set to {@link #CLOCK_USER} for {@link
* com.codahale.metrics.Clock.UserTimeClock} or {@link #CLOCK_CPU} for {@link CpuTimeClock}.
* If not set then the value of {@link Clock#defaultClock()} will be used.
*
*/
public static final class DefaultMeterSupplier
implements MetricRegistry.MetricSupplier, PluginInfoInitialized {
public Clock clk = Clock.defaultClock();
@Override
public void init(PluginInfo info) {
clk = getClock(info, CLOCK);
}
@Override
public Meter newMetric() {
return new Meter(clk);
}
}
public static Clock getClock(PluginInfo info, String param) {
if (info == null) {
return Clock.defaultClock();
}
String clock = null;
if (info.attributes != null) {
clock = info.attributes.get(param);
}
if (clock == null && info.initArgs != null) {
clock = (String) info.initArgs.get(param);
}
Clock clk = Clock.defaultClock();
if (clock != null) {
if (clock.equalsIgnoreCase(CLOCK_USER)) {
clk = USER_CLOCK;
} else if (clock.equalsIgnoreCase(CLOCK_CPU)) {
clk = CPU_CLOCK;
}
}
return clk;
}
/**
* Implementation class, must implement {@link Reservoir}. Supports non-standard configuration of
* the implementations available in metrics-core.
*/
public static final String RESERVOIR = "reservoir";
/** Size of reservoir. */
public static final String RESERVOIR_SIZE = "size";
/** Alpha parameter of {@link ExponentiallyDecayingReservoir}. */
public static final String RESERVOIR_EDR_ALPHA = "alpha";
/** Time window in seconds of {@link SlidingTimeWindowReservoir}. */
public static final String RESERVOIR_WINDOW = "window";
private static final String EDR_CLAZZ = ExponentiallyDecayingReservoir.class.getName();
private static final String UNI_CLAZZ = UniformReservoir.class.getName();
private static final String STW_CLAZZ = SlidingTimeWindowReservoir.class.getName();
private static final String SW_CLAZZ = SlidingWindowReservoir.class.getName();
private static final int DEFAULT_SIZE = 1028;
private static final double DEFAULT_ALPHA = 0.015;
private static final long DEFAULT_WINDOW = 300;
private static final Reservoir getReservoir(SolrResourceLoader loader, PluginInfo info) {
if (info == null) {
return new ExponentiallyDecayingReservoir();
}
Clock clk = getClock(info, CLOCK);
String clazz = ExponentiallyDecayingReservoir.class.getName();
int size = -1;
double alpha = -1;
long window = -1;
if (info.initArgs != null) {
if (info.initArgs.get(RESERVOIR) != null) {
String val = String.valueOf(info.initArgs.get(RESERVOIR)).trim();
if (!val.isEmpty()) {
clazz = val;
}
}
Number n = (Number) info.initArgs.get(RESERVOIR_SIZE);
if (n != null) {
size = n.intValue();
}
n = (Number) info.initArgs.get(RESERVOIR_EDR_ALPHA);
if (n != null) {
alpha = n.doubleValue();
}
n = (Number) info.initArgs.get(RESERVOIR_WINDOW);
if (n != null) {
window = n.longValue();
}
}
if (size <= 0) {
size = DEFAULT_SIZE;
}
if (alpha <= 0) {
alpha = DEFAULT_ALPHA;
}
// special case for core implementations
if (clazz.equals(EDR_CLAZZ)) {
return new ExponentiallyDecayingReservoir(size, alpha, clk);
} else if (clazz.equals(UNI_CLAZZ)) {
return new UniformReservoir(size);
} else if (clazz.equals(STW_CLAZZ)) {
if (window <= 0) {
window = DEFAULT_WINDOW; // 5 minutes, comparable to EDR
}
return new SlidingTimeWindowReservoir(window, TimeUnit.SECONDS);
} else if (clazz.equals(SW_CLAZZ)) {
return new SlidingWindowReservoir(size);
} else { // custom reservoir
Reservoir reservoir;
if (loader == null) {
return new ExponentiallyDecayingReservoir(size, alpha, clk);
} else {
try {
reservoir = loader.newInstance(clazz, Reservoir.class);
if (reservoir instanceof PluginInfoInitialized) {
((PluginInfoInitialized) reservoir).init(info);
} else {
SolrPluginUtils.invokeSetters(reservoir, info.initArgs, true);
}
return reservoir;
} catch (Exception e) {
log.warn(
"Error initializing custom Reservoir implementation (will use default): {}", info, e);
return new ExponentiallyDecayingReservoir(size, alpha, clk);
}
}
}
}
/**
* Default supplier of {@link Timer} instances, with configurable clock and reservoir. See {@link
* DefaultMeterSupplier} for clock configuration. Reservoir configuration uses {@link #RESERVOIR},
* {@link #RESERVOIR_EDR_ALPHA}, {@link #RESERVOIR_SIZE} and {@link #RESERVOIR_WINDOW}.
*/
public static final class DefaultTimerSupplier
implements MetricRegistry.MetricSupplier, PluginInfoInitialized {
public Clock clk = Clock.defaultClock();
private PluginInfo info;
private SolrResourceLoader loader;
public DefaultTimerSupplier(SolrResourceLoader loader) {
this.loader = loader;
}
@Override
public void init(PluginInfo info) {
clk = getClock(info, CLOCK);
this.info = info;
}
public Reservoir getReservoir() {
return MetricSuppliers.getReservoir(loader, info);
}
@Override
public Timer newMetric() {
return new Timer(getReservoir(), clk);
}
}
/** Default supplier of {@link Histogram} instances, with configurable reservoir. */
public static final class DefaultHistogramSupplier
implements MetricRegistry.MetricSupplier, PluginInfoInitialized {
private PluginInfo info;
private SolrResourceLoader loader;
public DefaultHistogramSupplier(SolrResourceLoader loader) {
this.loader = loader;
}
@Override
public void init(PluginInfo info) {
this.info = info;
}
public Reservoir getReservoir() {
return MetricSuppliers.getReservoir(loader, info);
}
@Override
public Histogram newMetric() {
return new Histogram(getReservoir());
}
}
/**
* Create a {@link Counter} supplier.
*
* @param loader resource loader
* @param info plugin configuration, or null for default
* @return configured supplier instance, or default instance if configuration was invalid
*/
@SuppressWarnings({"unchecked"})
public static MetricRegistry.MetricSupplier counterSupplier(
SolrResourceLoader loader, PluginInfo info) {
if (info == null || info.className == null || info.className.trim().isEmpty()) {
return new DefaultCounterSupplier();
}
if (MetricsConfig.NOOP_IMPL_CLASS.equals(info.className)) {
return NoOpCounterSupplier.INSTANCE;
}
MetricRegistry.MetricSupplier supplier;
if (loader == null) {
supplier = new DefaultCounterSupplier();
} else {
try {
supplier = loader.newInstance(info.className, MetricRegistry.MetricSupplier.class);
} catch (Exception e) {
log.warn("Error creating custom Counter supplier (will use default): {}", info, e);
supplier = new DefaultCounterSupplier();
}
}
if (supplier instanceof PluginInfoInitialized) {
((PluginInfoInitialized) supplier).init(info);
} else {
SolrPluginUtils.invokeSetters(supplier, info.initArgs, true);
}
return supplier;
}
/**
* Create a {@link Meter} supplier.
*
* @param loader resource loader
* @param info plugin configuration, or null for default
* @return configured supplier instance, or default instance if configuration was invalid
*/
@SuppressWarnings({"unchecked"})
public static MetricRegistry.MetricSupplier meterSupplier(
SolrResourceLoader loader, PluginInfo info) {
MetricRegistry.MetricSupplier supplier;
if (info == null || info.className == null || info.className.isEmpty()) {
supplier = new DefaultMeterSupplier();
} else {
if (MetricsConfig.NOOP_IMPL_CLASS.equals(info.className)) {
return NoOpMeterSupplier.INSTANCE;
}
if (loader == null) {
supplier = new DefaultMeterSupplier();
} else {
try {
supplier = loader.newInstance(info.className, MetricRegistry.MetricSupplier.class);
} catch (Exception e) {
log.warn("Error creating custom Meter supplier (will use default): {}", info, e);
supplier = new DefaultMeterSupplier();
}
}
}
if (supplier instanceof PluginInfoInitialized) {
((PluginInfoInitialized) supplier).init(info);
} else {
SolrPluginUtils.invokeSetters(supplier, info.initArgs, true);
}
return supplier;
}
/**
* Create a {@link Timer} supplier.
*
* @param loader resource loader
* @param info plugin configuration, or null for default
* @return configured supplier instance, or default instance if configuration was invalid
*/
@SuppressWarnings({"unchecked"})
public static MetricRegistry.MetricSupplier timerSupplier(
SolrResourceLoader loader, PluginInfo info) {
MetricRegistry.MetricSupplier supplier;
if (info == null || info.className == null || info.className.isEmpty()) {
supplier = new DefaultTimerSupplier(loader);
} else {
if (MetricsConfig.NOOP_IMPL_CLASS.equals(info.className)) {
return NoOpTimerSupplier.INSTANCE;
}
if (loader == null) {
supplier = new DefaultTimerSupplier(null);
} else {
try {
supplier = loader.newInstance(info.className, MetricRegistry.MetricSupplier.class);
} catch (Exception e) {
log.warn("Error creating custom Timer supplier (will use default): {}", info, e);
supplier = new DefaultTimerSupplier(loader);
}
}
}
if (supplier instanceof PluginInfoInitialized) {
((PluginInfoInitialized) supplier).init(info);
} else {
SolrPluginUtils.invokeSetters(supplier, info.initArgs, true);
}
return supplier;
}
/**
* Create a {@link Histogram} supplier.
*
* @param info plugin configuration, or null for default
* @return configured supplier instance, or default instance if configuration was invalid
*/
@SuppressWarnings({"unchecked"})
public static MetricRegistry.MetricSupplier histogramSupplier(
SolrResourceLoader loader, PluginInfo info) {
MetricRegistry.MetricSupplier supplier;
if (info == null || info.className == null || info.className.isEmpty()) {
supplier = new DefaultHistogramSupplier(loader);
} else {
if (MetricsConfig.NOOP_IMPL_CLASS.equals(info.className)) {
return NoOpHistogramSupplier.INSTANCE;
}
if (loader == null) {
supplier = new DefaultHistogramSupplier(null);
} else {
try {
supplier = loader.newInstance(info.className, MetricRegistry.MetricSupplier.class);
} catch (Exception e) {
log.warn("Error creating custom Histogram supplier (will use default): {}", info, e);
supplier = new DefaultHistogramSupplier(loader);
}
}
}
if (supplier instanceof PluginInfoInitialized) {
((PluginInfoInitialized) supplier).init(info);
} else {
SolrPluginUtils.invokeSetters(supplier, info.initArgs, true);
}
return supplier;
}
// NO-OP implementations. These can be used to effectively "turn off" the metrics
// collection, or rather eliminate the overhead of the metrics accounting and their
// memory footprint.
/** Marker interface for all no-op metrics. */
public interface NoOpMetric {}
/** No-op implementation of {@link Counter} supplier. */
public static final class NoOpCounterSupplier implements MetricRegistry.MetricSupplier {
public static final NoOpCounterSupplier INSTANCE = new NoOpCounterSupplier();
private static final class NoOpCounter extends Counter implements NoOpMetric {
@Override
public void inc() {
// no-op
}
@Override
public void inc(long n) {
// no-op
}
@Override
public void dec() {
// no-op
}
@Override
public void dec(long n) {
// no-op
}
}
private static final Counter METRIC = new NoOpCounter();
@Override
public Counter newMetric() {
return METRIC;
}
}
/** No-op implementation of {@link Histogram} supplier. */
public static final class NoOpHistogramSupplier
implements MetricRegistry.MetricSupplier {
public static final NoOpHistogramSupplier INSTANCE = new NoOpHistogramSupplier();
private static final class NoOpHistogram extends Histogram implements NoOpMetric {
public NoOpHistogram() {
super(new UniformReservoir(1));
}
@Override
public void update(int value) {
// no-op
}
@Override
public void update(long value) {
// no-op
}
}
private static final NoOpHistogram METRIC = new NoOpHistogram();
@Override
public Histogram newMetric() {
return METRIC;
}
}
/** No-op implementation of {@link Meter} supplier. */
public static final class NoOpMeterSupplier implements MetricRegistry.MetricSupplier {
public static final NoOpMeterSupplier INSTANCE = new NoOpMeterSupplier();
private static final class NoOpMeter extends Meter implements NoOpMetric {
@Override
public void mark() {
// no-op
}
@Override
public void mark(long n) {
// no-op
}
}
private static final NoOpMeter METRIC = new NoOpMeter();
@Override
public Meter newMetric() {
return METRIC;
}
}
/** No-op implementation of {@link Timer} supplier. */
public static final class NoOpTimerSupplier implements MetricRegistry.MetricSupplier {
public static final NoOpTimerSupplier INSTANCE = new NoOpTimerSupplier();
private static final class NoOpTimer extends Timer implements NoOpMetric {
@Override
public void update(long duration, TimeUnit unit) {
// no-op
}
@Override
public void update(Duration duration) {
// no-op
}
@Override
public T time(Callable event) throws Exception {
return event.call();
}
@Override
public T timeSupplier(Supplier event) {
return event.get();
}
@Override
public void time(Runnable event) {
event.run();
}
}
private static final NoOpTimer METRIC = new NoOpTimer();
@Override
public Timer newMetric() {
return METRIC;
}
}
/** No-op implementation of {@link Gauge}. */
public static final class NoOpGauge implements Gauge