/* * 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 java.lang.invoke.MethodHandles; import java.util.concurrent.TimeUnit; import com.codahale.metrics.Clock; import com.codahale.metrics.Counter; import com.codahale.metrics.ExponentiallyDecayingReservoir; 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 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. * <p>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.</p> */ 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<Counter> { @Override public Counter newMetric() { return new Counter(); } } private static final Clock CPU_CLOCK = new Clock.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: * <ul> * <li>clock - (string) can be set to {@link #CLOCK_USER} for {@link com.codahale.metrics.Clock.UserTimeClock} or * {@link #CLOCK_CPU} for {@link com.codahale.metrics.Clock.CpuTimeClock}. If not set then the value of * {@link Clock#defaultClock()} will be used.</li> * </ul> */ public static final class DefaultMeterSupplier implements MetricRegistry.MetricSupplier<Meter>, PluginInfoInitialized { public Clock clk = Clock.defaultClock(); @Override public void init(PluginInfo info) { clk = getClock(info, CLOCK); } @Override public Meter newMetric() { return new Meter(clk); } } private 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; 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<Timer>, 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<Histogram>, 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 */ public static MetricRegistry.MetricSupplier<Counter> counterSupplier(SolrResourceLoader loader, PluginInfo info) { if (info == null || info.className == null || info.className.trim().isEmpty()) { return new DefaultCounterSupplier(); } MetricRegistry.MetricSupplier<Counter> supplier; 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 */ public static MetricRegistry.MetricSupplier<Meter> meterSupplier(SolrResourceLoader loader, PluginInfo info) { MetricRegistry.MetricSupplier<Meter> supplier; if (info == null || info.className == null || info.className.isEmpty()) { 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 */ public static MetricRegistry.MetricSupplier<Timer> timerSupplier(SolrResourceLoader loader, PluginInfo info) { MetricRegistry.MetricSupplier<Timer> supplier; if (info == null || info.className == null || info.className.isEmpty()) { supplier = new DefaultTimerSupplier(loader); } 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 */ public static MetricRegistry.MetricSupplier<Histogram> histogramSupplier(SolrResourceLoader loader, PluginInfo info) { MetricRegistry.MetricSupplier<Histogram> supplier; if (info == null || info.className == null || info.className.isEmpty()) { supplier = new DefaultHistogramSupplier(loader); } 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; } }