You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2017/04/26 09:41:17 UTC

lucene-solr:jira/solr-10262: SOLR-10262 Make metric implementations configurable.

Repository: lucene-solr
Updated Branches:
  refs/heads/jira/solr-10262 [created] 555a14968


SOLR-10262 Make metric implementations configurable.


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/555a1496
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/555a1496
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/555a1496

Branch: refs/heads/jira/solr-10262
Commit: 555a149688d39f90ef2897d5a8f6e9bc59f89a33
Parents: 680f4d7
Author: Andrzej Bialecki <ab...@apache.org>
Authored: Wed Apr 26 11:40:40 2017 +0200
Committer: Andrzej Bialecki <ab...@apache.org>
Committed: Wed Apr 26 11:40:40 2017 +0200

----------------------------------------------------------------------
 .../org/apache/solr/core/CoreContainer.java     |  11 +-
 .../org/apache/solr/core/MetricsConfig.java     | 121 +++++++
 .../java/org/apache/solr/core/NodeConfig.java   |  32 +-
 .../org/apache/solr/core/SolrXmlConfig.java     |  29 +-
 .../apache/solr/metrics/MetricSuppliers.java    | 363 +++++++++++++++++++
 .../solr/metrics/SolrCoreMetricManager.java     |   2 +-
 .../apache/solr/metrics/SolrMetricManager.java  |  45 ++-
 .../apache/solr/servlet/SolrDispatchFilter.java |   2 +-
 .../org/apache/solr/util/SolrPluginUtils.java   |  21 +-
 .../src/test-files/solr/solr-metricsconfig.xml  |  61 ++++
 .../org/apache/solr/metrics/JvmMetricsTest.java |   4 +-
 .../apache/solr/metrics/MetricsConfigTest.java  | 126 +++++++
 .../solr/metrics/MockCounterSupplier.java       |  36 ++
 .../solr/metrics/MockHistogramSupplier.java     |  40 ++
 .../apache/solr/metrics/MockMeterSupplier.java  |  36 ++
 .../apache/solr/metrics/MockTimerSupplier.java  |  46 +++
 .../metrics/SolrMetricsIntegrationTest.java     |   2 +-
 .../java/org/apache/solr/util/TestHarness.java  |   6 +-
 18 files changed, 940 insertions(+), 43 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/java/org/apache/solr/core/CoreContainer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 28c1eaf..3ff5135 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -479,7 +479,7 @@ public class CoreContainer {
       }
     }
 
-    metricManager = new SolrMetricManager();
+    metricManager = new SolrMetricManager(loader, cfg.getMetricsConfig());
 
     coreContainerWorkExecutor = MetricUtils.instrumentedExecutorService(
         coreContainerWorkExecutor, null,
@@ -525,9 +525,10 @@ public class CoreContainer {
     if(pkiAuthenticationPlugin != null)
       containerHandlers.put(PKIAuthenticationPlugin.PATH, pkiAuthenticationPlugin.getRequestHandler());
 
-    metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, null, SolrInfoBean.Group.node);
-    metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, null, SolrInfoBean.Group.jvm);
-    metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, null, SolrInfoBean.Group.jetty);
+    PluginInfo[] metricReporters = cfg.getMetricsConfig().getMetricReporters();
+    metricManager.loadReporters(metricReporters, loader, null, SolrInfoBean.Group.node);
+    metricManager.loadReporters(metricReporters, loader, null, SolrInfoBean.Group.jvm);
+    metricManager.loadReporters(metricReporters, loader, null, SolrInfoBean.Group.jetty);
 
     coreConfigService = ConfigSetService.createConfigSetService(cfg, loader, zkSys.zkController);
 
@@ -556,7 +557,7 @@ public class CoreContainer {
     fieldCacheBean.initializeMetrics(metricManager, registryName, null);
 
     if (isZooKeeperAware()) {
-      metricManager.loadClusterReporters(cfg.getMetricReporterPlugins(), this);
+      metricManager.loadClusterReporters(metricReporters, this);
     }
 
     // setup executor to load cores in parallel

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/java/org/apache/solr/core/MetricsConfig.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/MetricsConfig.java b/solr/core/src/java/org/apache/solr/core/MetricsConfig.java
new file mode 100644
index 0000000..796483b
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/core/MetricsConfig.java
@@ -0,0 +1,121 @@
+/*
+ * 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.core;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ *
+ */
+public class MetricsConfig {
+
+  private final PluginInfo[] metricReporters;
+  private final Set<String> hiddenSysProps;
+  private final PluginInfo counterSupplier;
+  private final PluginInfo meterSupplier;
+  private final PluginInfo timerSupplier;
+  private final PluginInfo histogramSupplier;
+
+  private MetricsConfig(PluginInfo[] metricReporters, Set<String> hiddenSysProps,
+                        PluginInfo counterSupplier, PluginInfo meterSupplier,
+                        PluginInfo timerSupplier, PluginInfo histogramSupplier) {
+    this.metricReporters = metricReporters;
+    this.hiddenSysProps = hiddenSysProps;
+    this.counterSupplier = counterSupplier;
+    this.meterSupplier = meterSupplier;
+    this.timerSupplier = timerSupplier;
+    this.histogramSupplier = histogramSupplier;
+  }
+
+  public PluginInfo[] getMetricReporters() {
+    return metricReporters;
+  }
+
+  public Set<String> getHiddenSysProps() {
+    return hiddenSysProps;
+  }
+
+  public PluginInfo getCounterSupplier() {
+    return counterSupplier;
+  }
+
+  public PluginInfo getMeterSupplier() {
+    return meterSupplier;
+  }
+
+  public PluginInfo getTimerSupplier() {
+    return timerSupplier;
+  }
+
+  public PluginInfo getHistogramSupplier() {
+    return histogramSupplier;
+  }
+
+  public static class MetricsConfigBuilder {
+    private PluginInfo[] metricReporterPlugins = new PluginInfo[0];
+    private Set<String> hiddenSysProps = new HashSet<>();
+    private PluginInfo counterSupplier;
+    private PluginInfo meterSupplier;
+    private PluginInfo timerSupplier;
+    private PluginInfo histogramSupplier;
+
+    public MetricsConfigBuilder() {
+
+    }
+
+    public MetricsConfigBuilder setHiddenSysProps(Set<String> hiddenSysProps) {
+      if (hiddenSysProps != null && !hiddenSysProps.isEmpty()) {
+        this.hiddenSysProps.clear();
+        this.hiddenSysProps.addAll(hiddenSysProps);
+      }
+      return this;
+    }
+
+    public MetricsConfigBuilder setMetricReporterPlugins(PluginInfo[] metricReporterPlugins) {
+      this.metricReporterPlugins = metricReporterPlugins != null ? metricReporterPlugins : new PluginInfo[0];
+      return this;
+    }
+
+    public MetricsConfigBuilder setCounterSupplier(PluginInfo info) {
+      this.counterSupplier = info;
+      return this;
+    }
+
+    public MetricsConfigBuilder setMeterSupplier(PluginInfo info) {
+      this.meterSupplier = info;
+      return this;
+    }
+
+    public MetricsConfigBuilder setTimerSupplier(PluginInfo info) {
+      this.timerSupplier = info;
+      return this;
+    }
+
+    public MetricsConfigBuilder setHistogramSupplier(PluginInfo info) {
+      this.histogramSupplier = info;
+      return this;
+    }
+
+    public MetricsConfig build() {
+      return new MetricsConfig(metricReporterPlugins, hiddenSysProps, counterSupplier, meterSupplier,
+          timerSupplier, histogramSupplier);
+    }
+
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/java/org/apache/solr/core/NodeConfig.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/NodeConfig.java b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
index 7a209d0..2b70130 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
@@ -65,9 +65,7 @@ public class NodeConfig {
 
   private final PluginInfo[] backupRepositoryPlugins;
 
-  private final PluginInfo[] metricReporterPlugins;
-
-  private final Set<String> hiddenSysProps;
+  private final MetricsConfig metricsConfig;
 
   private final PluginInfo transientCacheConfig;
 
@@ -78,7 +76,7 @@ public class NodeConfig {
                      LogWatcherConfig logWatcherConfig, CloudConfig cloudConfig, Integer coreLoadThreads,
                      int transientCacheSize, boolean useSchemaCache, String managementPath, SolrResourceLoader loader,
                      Properties solrProperties, PluginInfo[] backupRepositoryPlugins,
-                     PluginInfo[] metricReporterPlugins, Set<String> hiddenSysProps, PluginInfo transientCacheConfig) {
+                     MetricsConfig metricsConfig, PluginInfo transientCacheConfig) {
     this.nodeName = nodeName;
     this.coreRootDirectory = coreRootDirectory;
     this.configSetBaseDirectory = configSetBaseDirectory;
@@ -98,8 +96,7 @@ public class NodeConfig {
     this.loader = loader;
     this.solrProperties = solrProperties;
     this.backupRepositoryPlugins = backupRepositoryPlugins;
-    this.metricReporterPlugins = metricReporterPlugins;
-    this.hiddenSysProps = hiddenSysProps;
+    this.metricsConfig = metricsConfig;
     this.transientCacheConfig = transientCacheConfig;
 
     if (this.cloudConfig != null && this.getCoreLoadThreadCount(false) < 2) {
@@ -189,12 +186,8 @@ public class NodeConfig {
     return backupRepositoryPlugins;
   }
 
-  public PluginInfo[] getMetricReporterPlugins() {
-    return metricReporterPlugins;
-  }
-
-  public Set<String> getHiddenSysProps() {
-    return hiddenSysProps;
+  public MetricsConfig getMetricsConfig() {
+    return metricsConfig;
   }
 
   public PluginInfo getTransientCachePluginInfo() { return transientCacheConfig; }
@@ -220,8 +213,7 @@ public class NodeConfig {
     private String managementPath;
     private Properties solrProperties = new Properties();
     private PluginInfo[] backupRepositoryPlugins;
-    private PluginInfo[] metricReporterPlugins;
-    private Set<String> hiddenSysProps = new HashSet<>(DEFAULT_HIDDEN_SYS_PROPS);
+    private MetricsConfig metricsConfig;
     private PluginInfo transientCacheConfig;
 
     private final SolrResourceLoader loader;
@@ -251,6 +243,7 @@ public class NodeConfig {
       this.loader = loader;
       this.coreRootDirectory = loader.getInstancePath();
       this.configSetBaseDirectory = loader.getInstancePath().resolve("configsets");
+      this.metricsConfig = new MetricsConfig.MetricsConfigBuilder().build();
     }
 
     public NodeConfigBuilder setCoreRootDirectory(String coreRootDirectory) {
@@ -340,8 +333,8 @@ public class NodeConfig {
       return this;
     }
 
-    public NodeConfigBuilder setMetricReporterPlugins(PluginInfo[] metricReporterPlugins) {
-      this.metricReporterPlugins = metricReporterPlugins;
+    public NodeConfigBuilder setMetricsConfig(MetricsConfig metricsConfig) {
+      this.metricsConfig = metricsConfig;
       return this;
     }
     
@@ -350,16 +343,11 @@ public class NodeConfig {
       return this;
     }
 
-    public NodeConfigBuilder setHiddenSysProps(Set<String> hiddenSysProps) {
-      this.hiddenSysProps = hiddenSysProps;
-      return this;
-    }
-
     public NodeConfig build() {
       return new NodeConfig(nodeName, coreRootDirectory, configSetBaseDirectory, sharedLibDirectory, shardHandlerFactoryConfig,
                             updateShardHandlerConfig, coreAdminHandlerClass, collectionsAdminHandlerClass, infoHandlerClass, configSetsHandlerClass,
                             logWatcherConfig, cloudConfig, coreLoadThreads, transientCacheSize, useSchemaCache, managementPath, loader, solrProperties,
-                            backupRepositoryPlugins, metricReporterPlugins, hiddenSysProps, transientCacheConfig);
+                            backupRepositoryPlugins, metricsConfig, transientCacheConfig);
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
index 65f29a4..e783a89 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
@@ -103,8 +103,7 @@ public class SolrXmlConfig {
     if (cloudConfig != null)
       configBuilder.setCloudConfig(cloudConfig);
     configBuilder.setBackupRepositoryPlugins(getBackupRepositoryPluginInfos(config));
-    configBuilder.setMetricReporterPlugins(getMetricReporterPluginInfos(config));
-    configBuilder.setHiddenSysProps(getHiddenSysProps(config));
+    configBuilder.setMetricsConfig(getMetricsConfig(config));
     return fillSolrSection(configBuilder, entries);
   }
 
@@ -461,6 +460,32 @@ public class SolrXmlConfig {
     return configs;
   }
 
+  private static MetricsConfig getMetricsConfig(Config config) {
+    MetricsConfig.MetricsConfigBuilder builder = new MetricsConfig.MetricsConfigBuilder();
+    Node node = config.getNode("solr/metrics/suppliers/counter", false);
+    if (node != null) {
+      builder = builder.setCounterSupplier(new PluginInfo(node, "counterSupplier", false, false));
+    }
+    node = config.getNode("solr/metrics/suppliers/meter", false);
+    if (node != null) {
+      builder = builder.setMeterSupplier(new PluginInfo(node, "meterSupplier", false, false));
+    }
+    node = config.getNode("solr/metrics/suppliers/timer", false);
+    if (node != null) {
+      builder = builder.setTimerSupplier(new PluginInfo(node, "timerSupplier", false, false));
+    }
+    node = config.getNode("solr/metrics/suppliers/histogram", false);
+    if (node != null) {
+      builder = builder.setHistogramSupplier(new PluginInfo(node, "histogramSupplier", false, false));
+    }
+    PluginInfo[] reporterPlugins = getMetricReporterPluginInfos(config);
+    Set<String> hiddenSysProps = getHiddenSysProps(config);
+    return builder
+        .setMetricReporterPlugins(reporterPlugins)
+        .setHiddenSysProps(hiddenSysProps)
+        .build();
+  }
+
   private static PluginInfo[] getMetricReporterPluginInfos(Config config) {
     NodeList nodes = (NodeList) config.evaluate("solr/metrics/reporter", XPathConstants.NODESET);
     List<PluginInfo> configs = new ArrayList<>();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/java/org/apache/solr/metrics/MetricSuppliers.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/MetricSuppliers.java b/solr/core/src/java/org/apache/solr/metrics/MetricSuppliers.java
new file mode 100644
index 0000000..c444f66
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/MetricSuppliers.java
@@ -0,0 +1,363 @@
+/*
+ * 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 Meter 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 Meter 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;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
index 8de053c..c7b6318 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -82,7 +82,7 @@ public class SolrCoreMetricManager implements Closeable {
    */
   public void loadReporters() {
     NodeConfig nodeConfig = core.getCoreContainer().getConfig();
-    PluginInfo[] pluginInfos = nodeConfig.getMetricReporterPlugins();
+    PluginInfo[] pluginInfos = nodeConfig.getMetricsConfig().getMetricReporters();
     metricManager.loadReporters(pluginInfos, core.getResourceLoader(), tag,
         SolrInfoBean.Group.core, registryName);
     if (cloudMode) {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
index d4eb06a..c041d1f 100644
--- a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -49,6 +49,7 @@ import com.codahale.metrics.SharedMetricRegistries;
 import com.codahale.metrics.Timer;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.MetricsConfig;
 import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrInfoBean;
@@ -102,7 +103,41 @@ public class SolrMetricManager {
 
   public static final int DEFAULT_CLOUD_REPORTER_PERIOD = 60;
 
-  public SolrMetricManager() { }
+  private MetricRegistry.MetricSupplier<Counter> counterSupplier;
+  private MetricRegistry.MetricSupplier<Meter> meterSupplier;
+  private MetricRegistry.MetricSupplier<Timer> timerSupplier;
+  private MetricRegistry.MetricSupplier<Histogram> histogramSupplier;
+
+  public SolrMetricManager() {
+    counterSupplier = MetricSuppliers.counterSupplier(null, null);
+    meterSupplier = MetricSuppliers.meterSupplier(null, null);
+    timerSupplier = MetricSuppliers.timerSupplier(null, null);
+    histogramSupplier = MetricSuppliers.histogramSupplier(null, null);
+  }
+
+  public SolrMetricManager(SolrResourceLoader loader, MetricsConfig metricsConfig) {
+    counterSupplier = MetricSuppliers.counterSupplier(loader, metricsConfig.getCounterSupplier());
+    meterSupplier = MetricSuppliers.meterSupplier(loader, metricsConfig.getMeterSupplier());
+    timerSupplier = MetricSuppliers.timerSupplier(loader, metricsConfig.getTimerSupplier());
+    histogramSupplier = MetricSuppliers.histogramSupplier(loader, metricsConfig.getHistogramSupplier());
+  }
+
+  // for unit tests
+  public MetricRegistry.MetricSupplier<Counter> getCounterSupplier() {
+    return counterSupplier;
+  }
+
+  public MetricRegistry.MetricSupplier<Meter> getMeterSupplier() {
+    return meterSupplier;
+  }
+
+  public MetricRegistry.MetricSupplier<Timer> getTimerSupplier() {
+    return timerSupplier;
+  }
+
+  public MetricRegistry.MetricSupplier<Histogram> getHistogramSupplier() {
+    return histogramSupplier;
+  }
 
   /**
    * An implementation of {@link MetricFilter} that selects metrics
@@ -539,7 +574,7 @@ public class SolrMetricManager {
     if (info != null) {
       info.registerMetricName(name);
     }
-    return registry(registry).meter(name);
+    return registry(registry).meter(name, meterSupplier);
   }
 
   /**
@@ -555,7 +590,7 @@ public class SolrMetricManager {
     if (info != null) {
       info.registerMetricName(name);
     }
-    return registry(registry).timer(name);
+    return registry(registry).timer(name, timerSupplier);
   }
 
   /**
@@ -571,7 +606,7 @@ public class SolrMetricManager {
     if (info != null) {
       info.registerMetricName(name);
     }
-    return registry(registry).counter(name);
+    return registry(registry).counter(name, counterSupplier);
   }
 
   /**
@@ -587,7 +622,7 @@ public class SolrMetricManager {
     if (info != null) {
       info.registerMetricName(name);
     }
-    return registry(registry).histogram(name);
+    return registry(registry).histogram(name, histogramSupplier);
   }
 
   /**

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
index 39ccadc..5905280 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
@@ -186,7 +186,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
 
   private void setupJvmMetrics()  {
     SolrMetricManager metricManager = cores.getMetricManager();
-    final Set<String> hiddenSysProps = cores.getConfig().getHiddenSysProps();
+    final Set<String> hiddenSysProps = cores.getConfig().getMetricsConfig().getHiddenSysProps();
     try {
       String registry = SolrMetricManager.getRegistryName(SolrInfoBean.Group.jvm);
       metricManager.registerAll(registry, new AltBufferPoolMetricSet(), true, "buffers");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/java/org/apache/solr/util/SolrPluginUtils.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/util/SolrPluginUtils.java b/solr/core/src/java/org/apache/solr/util/SolrPluginUtils.java
index 6a9f679..658b035 100644
--- a/solr/core/src/java/org/apache/solr/util/SolrPluginUtils.java
+++ b/solr/core/src/java/org/apache/solr/util/SolrPluginUtils.java
@@ -1070,6 +1070,10 @@ public class SolrPluginUtils {
 
 
   public static void invokeSetters(Object bean, Iterable<Map.Entry<String,Object>> initArgs) {
+    invokeSetters(bean, initArgs, false);
+  }
+
+  public static void invokeSetters(Object bean, Iterable<Map.Entry<String,Object>> initArgs, boolean lenient) {
     if (initArgs == null) return;
     final Class<?> clazz = bean.getClass();
     for (Map.Entry<String,Object> entry : initArgs) {
@@ -1077,19 +1081,27 @@ public class SolrPluginUtils {
       String setterName = "set" + String.valueOf(Character.toUpperCase(key.charAt(0))) + key.substring(1);
       try {
         final Object val = entry.getValue();
-        final Method method = findSetter(clazz, setterName, key, val.getClass());
-        method.invoke(bean, val);
+        final Method method = findSetter(clazz, setterName, key, val.getClass(), lenient);
+        if (method != null) {
+          method.invoke(bean, val);
+        }
       } catch (InvocationTargetException | IllegalAccessException e1) {
+        if (lenient) {
+          continue;
+        }
         throw new RuntimeException("Error invoking setter " + setterName + " on class : " + clazz.getName(), e1);
       }
     }
   }
 
-  private static Method findSetter(Class<?> clazz, String setterName, String key, Class<?> paramClazz) {
+  private static Method findSetter(Class<?> clazz, String setterName, String key, Class<?> paramClazz, boolean lenient) {
     BeanInfo beanInfo;
     try {
       beanInfo = Introspector.getBeanInfo(clazz);
     } catch (IntrospectionException ie) {
+      if (lenient) {
+        return null;
+      }
       throw new RuntimeException("Error getting bean info for class : " + clazz.getName(), ie);
     }
     for (final boolean matchParamClazz: new boolean[]{true, false}) {
@@ -1102,6 +1114,9 @@ public class SolrPluginUtils {
         }
       }
     }
+    if (lenient) {
+      return null;
+    }
     throw new RuntimeException("No setter corrresponding to '" + key + "' in " + clazz.getName());
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/test-files/solr/solr-metricsconfig.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/solr-metricsconfig.xml b/solr/core/src/test-files/solr/solr-metricsconfig.xml
new file mode 100644
index 0000000..6edb2fa
--- /dev/null
+++ b/solr/core/src/test-files/solr/solr-metricsconfig.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ 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.
+-->
+
+<solr>
+ <metrics>
+  <suppliers>
+    <counter class="${counter.class:}">
+      <str name="foo">bar</str>
+      <int name="wxt">100</int>
+      <bool name="flag">true</bool>
+    </counter>
+    <meter class="${meter.class:}">
+      <str name="foo">bar</str>
+      <str name="clock">${clock:user}</str>
+    </meter>
+    <timer class="${timer.class:}">
+      <str name="foo">bar</str>
+      <str name="clock">${clock:user}</str>
+      <str name="reservoir">${timer.reservoir:}</str>
+      <int name="size">${histogram.size:-1}</int>
+      <double name="alpha">${histogram.alpha:-1}</double>
+      <long name="window">${histogram.window:-1}</long>
+      <str name="strParam">strParam</str>
+      <int name="intParam">-100</int>
+      <bool name="boolParam">true</bool>
+    </timer>
+    <histogram class="${histogram.class:}">
+      <str name="foo">bar</str>
+      <str name="clock">${clock:user}</str>
+      <str name="reservoir">${histogram.reservoir:}</str>
+      <int name="size">${histogram.size:-1}</int>
+      <double name="alpha">${histogram.alpha:-1}</double>
+      <long name="window">${histogram.window:-1}</long>
+    </histogram>
+  </suppliers>
+  <hiddenSysProps>
+    <str>foo</str>
+    <str>bar</str>
+    <str>baz</str>
+  </hiddenSysProps>
+  <!-- this reporter doesn't specify 'group' or 'registry', it will be instantiated for any group. -->
+  <reporter name="universal" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+ </metrics>
+</solr>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java
index 020fe5e..d40bc63 100644
--- a/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/JvmMetricsTest.java
@@ -124,14 +124,14 @@ public class JvmMetricsTest extends SolrJettyTestBase {
     String solrXml = FileUtils.readFileToString(Paths.get(home.toString(), "solr.xml").toFile(), "UTF-8");
     NodeConfig config = SolrXmlConfig.fromString(loader, solrXml);
     NodeConfig.NodeConfigBuilder.DEFAULT_HIDDEN_SYS_PROPS.forEach(s -> {
-      assertTrue(s, config.getHiddenSysProps().contains(s));
+      assertTrue(s, config.getMetricsConfig().getHiddenSysProps().contains(s));
     });
 
     // custom config
     solrXml = FileUtils.readFileToString(Paths.get(home.toString(), "solr-hiddensysprops.xml").toFile(), "UTF-8");
     NodeConfig config2 = SolrXmlConfig.fromString(loader, solrXml);
     Arrays.asList("foo", "bar", "baz").forEach(s -> {
-      assertTrue(s, config2.getHiddenSysProps().contains(s));
+      assertTrue(s, config2.getMetricsConfig().getHiddenSysProps().contains(s));
     });
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java b/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java
new file mode 100644
index 0000000..789e95a
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/MetricsConfigTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.io.File;
+import java.io.InputStream;
+
+import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
+import com.codahale.metrics.Clock;
+import com.codahale.metrics.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.Reservoir;
+import com.codahale.metrics.SlidingTimeWindowReservoir;
+import com.codahale.metrics.UniformReservoir;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.core.NodeConfig;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.core.SolrXmlConfig;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TestRule;
+
+/**
+ *
+ */
+public class MetricsConfigTest extends SolrTestCaseJ4 {
+  @Rule
+  public TestRule solrTestRules = RuleChain.outerRule(new SystemPropertiesRestoreRule());
+
+  // tmp dir, cleaned up automatically.
+  private static File solrHome = null;
+  private static SolrResourceLoader loader = null;
+
+  @BeforeClass
+  public static void setupLoader() throws Exception {
+    solrHome = createTempDir().toFile();
+    loader = new SolrResourceLoader(solrHome.toPath());
+  }
+
+  @AfterClass
+  public static void cleanupLoader() throws Exception {
+    solrHome = null;
+    loader = null;
+  }
+
+  @Test
+  public void testDefaults() throws Exception {
+    NodeConfig cfg = loadNodeConfig();
+    SolrMetricManager mgr = new SolrMetricManager(loader, cfg.getMetricsConfig());
+    assertTrue(mgr.getCounterSupplier() instanceof MetricSuppliers.DefaultCounterSupplier);
+    assertTrue(mgr.getMeterSupplier() instanceof MetricSuppliers.DefaultMeterSupplier);
+    assertTrue(mgr.getTimerSupplier() instanceof MetricSuppliers.DefaultTimerSupplier);
+    assertTrue(mgr.getHistogramSupplier() instanceof MetricSuppliers.DefaultHistogramSupplier);
+    Clock clk = ((MetricSuppliers.DefaultTimerSupplier)mgr.getTimerSupplier()).clk;
+    assertTrue(clk instanceof Clock.UserTimeClock);
+    Reservoir rsv = ((MetricSuppliers.DefaultTimerSupplier)mgr.getTimerSupplier()).getReservoir();
+    assertTrue(rsv instanceof ExponentiallyDecayingReservoir);
+  }
+
+  @Test
+  public void testCustomReservoir() throws Exception {
+    System.setProperty("timer.reservoir", UniformReservoir.class.getName());
+    System.setProperty("histogram.size", "2048");
+    System.setProperty("histogram.window", "600");
+    System.setProperty("histogram.reservoir", SlidingTimeWindowReservoir.class.getName());
+    NodeConfig cfg = loadNodeConfig();
+    SolrMetricManager mgr = new SolrMetricManager(loader, cfg.getMetricsConfig());
+    assertTrue(mgr.getCounterSupplier() instanceof MetricSuppliers.DefaultCounterSupplier);
+    assertTrue(mgr.getMeterSupplier() instanceof MetricSuppliers.DefaultMeterSupplier);
+    assertTrue(mgr.getTimerSupplier() instanceof MetricSuppliers.DefaultTimerSupplier);
+    assertTrue(mgr.getHistogramSupplier() instanceof MetricSuppliers.DefaultHistogramSupplier);
+    Reservoir rsv = ((MetricSuppliers.DefaultTimerSupplier)mgr.getTimerSupplier()).getReservoir();
+    assertTrue(rsv instanceof UniformReservoir);
+    rsv = ((MetricSuppliers.DefaultHistogramSupplier)mgr.getHistogramSupplier()).getReservoir();
+    assertTrue(rsv instanceof SlidingTimeWindowReservoir);
+  }
+
+  @Test
+  public void testCustomSupplier() throws Exception {
+    System.setProperty("counter.class", MockCounterSupplier.class.getName());
+    System.setProperty("meter.class", MockMeterSupplier.class.getName());
+    System.setProperty("timer.class", MockTimerSupplier.class.getName());
+    System.setProperty("histogram.class", MockHistogramSupplier.class.getName());
+    NodeConfig cfg = loadNodeConfig();
+    SolrMetricManager mgr = new SolrMetricManager(loader, cfg.getMetricsConfig());
+    assertTrue(mgr.getCounterSupplier() instanceof MockCounterSupplier);
+    assertTrue(mgr.getMeterSupplier() instanceof MockMeterSupplier);
+    assertTrue(mgr.getTimerSupplier() instanceof MockTimerSupplier);
+    assertTrue(mgr.getHistogramSupplier() instanceof MockHistogramSupplier);
+
+    // assert setter-based configuration
+    MockCounterSupplier mockCounterSupplier = ((MockCounterSupplier)mgr.getCounterSupplier());
+    assertEquals("bar", mockCounterSupplier.foo);
+    MockMeterSupplier mockMeterSupplier = ((MockMeterSupplier)mgr.getMeterSupplier());
+    assertEquals("bar", mockMeterSupplier.foo);
+    MockTimerSupplier mockTimerSupplier = ((MockTimerSupplier)mgr.getTimerSupplier());
+    assertEquals(true, mockTimerSupplier.boolParam);
+    assertEquals("strParam", mockTimerSupplier.strParam);
+    assertEquals(-100, mockTimerSupplier.intParam);
+
+    // assert PluginInfoInitialized-based configuration
+    MockHistogramSupplier mockHistogramSupplier = ((MockHistogramSupplier)mgr.getHistogramSupplier());
+    assertNotNull(mockHistogramSupplier.info);
+  }
+
+  private NodeConfig loadNodeConfig() throws Exception {
+    InputStream is = MetricsConfigTest.class.getResourceAsStream("/solr/solr-metricsconfig.xml");
+    return SolrXmlConfig.fromInputStream(loader, is);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/test/org/apache/solr/metrics/MockCounterSupplier.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/MockCounterSupplier.java b/solr/core/src/test/org/apache/solr/metrics/MockCounterSupplier.java
new file mode 100644
index 0000000..d6ca407
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/MockCounterSupplier.java
@@ -0,0 +1,36 @@
+/*
+ * 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.Counter;
+import com.codahale.metrics.MetricRegistry;
+
+/**
+ *
+ */
+public class MockCounterSupplier implements MetricRegistry.MetricSupplier<Counter> {
+  public String foo;
+
+  public void setFoo(String foo) {
+    this.foo = foo;
+  }
+
+  @Override
+  public Counter newMetric() {
+    return new Counter();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/test/org/apache/solr/metrics/MockHistogramSupplier.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/MockHistogramSupplier.java b/solr/core/src/test/org/apache/solr/metrics/MockHistogramSupplier.java
new file mode 100644
index 0000000..3df72af
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/MockHistogramSupplier.java
@@ -0,0 +1,40 @@
+/*
+ * 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.ExponentiallyDecayingReservoir;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.MetricRegistry;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.util.plugin.PluginInfoInitialized;
+
+/**
+ *
+ */
+public class MockHistogramSupplier implements MetricRegistry.MetricSupplier<Histogram>, PluginInfoInitialized {
+  public PluginInfo info;
+
+  @Override
+  public Histogram newMetric() {
+    return new Histogram(new ExponentiallyDecayingReservoir());
+  }
+
+  @Override
+  public void init(PluginInfo info) {
+    this.info = info;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/test/org/apache/solr/metrics/MockMeterSupplier.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/MockMeterSupplier.java b/solr/core/src/test/org/apache/solr/metrics/MockMeterSupplier.java
new file mode 100644
index 0000000..c035244
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/MockMeterSupplier.java
@@ -0,0 +1,36 @@
+/*
+ * 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.Meter;
+import com.codahale.metrics.MetricRegistry;
+
+/**
+ *
+ */
+public class MockMeterSupplier implements MetricRegistry.MetricSupplier<Meter> {
+  public String foo;
+
+  public void setFoo(String foo) {
+    this.foo = foo;
+  }
+
+  @Override
+  public Meter newMetric() {
+    return new Meter();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/test/org/apache/solr/metrics/MockTimerSupplier.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/MockTimerSupplier.java b/solr/core/src/test/org/apache/solr/metrics/MockTimerSupplier.java
new file mode 100644
index 0000000..aa400d9
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/MockTimerSupplier.java
@@ -0,0 +1,46 @@
+/*
+ * 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.MetricRegistry;
+import com.codahale.metrics.Timer;
+
+/**
+ *
+ */
+public class MockTimerSupplier implements MetricRegistry.MetricSupplier<Timer> {
+  public boolean boolParam;
+  public String strParam;
+  public int intParam;
+
+  public void setBoolParam(boolean boolParam) {
+    this.boolParam = boolParam;
+  }
+
+  public void setStrParam(String strParam) {
+    this.strParam = strParam;
+  }
+
+  public void setIntParam(int intParam) {
+    this.intParam = intParam;
+  }
+
+  @Override
+  public Timer newMetric() {
+    return new Timer();
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
index 56dab37..a8e7ecd 100644
--- a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
@@ -86,7 +86,7 @@ public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
     cc.rename(DEFAULT_TEST_CORENAME, CORE_NAME);
     h.coreName = CORE_NAME;
     cfg = cc.getConfig();
-    PluginInfo[] plugins = cfg.getMetricReporterPlugins();
+    PluginInfo[] plugins = cfg.getMetricsConfig().getMetricReporters();
     assertNotNull(plugins);
     assertEquals(10 + jmxReporter, plugins.length);
     reporters = metricManager.getReporters("solr.node");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/555a1496/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
index cefd75f..4a6e90a 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
@@ -36,6 +36,7 @@ import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.CoreDescriptor;
 import org.apache.solr.core.CorePropertiesLocator;
 import org.apache.solr.core.CoresLocator;
+import org.apache.solr.core.MetricsConfig;
 import org.apache.solr.core.NodeConfig;
 import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrConfig;
@@ -197,12 +198,15 @@ public class TestHarness extends BaseTestHarness {
     attributes.put("name", "default");
     attributes.put("class", SolrJmxReporter.class.getName());
     PluginInfo defaultPlugin = new PluginInfo("reporter", attributes);
+    MetricsConfig metricsConfig = new MetricsConfig.MetricsConfigBuilder()
+        .setMetricReporterPlugins(new PluginInfo[] {defaultPlugin})
+        .build();
 
     return new NodeConfig.NodeConfigBuilder("testNode", loader)
         .setUseSchemaCache(Boolean.getBoolean("shareSchema"))
         .setCloudConfig(cloudConfig)
         .setUpdateShardHandlerConfig(updateShardHandlerConfig)
-        .setMetricReporterPlugins(new PluginInfo[] {defaultPlugin})
+        .setMetricsConfig(metricsConfig)
         .build();
   }