lucene-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Andrzej Białecki <andrzej.biale...@lucidworks.com>
Subject Re: lucene-solr:branch_6_6: SOLR-11221: SolrJmxReporter broken on core reload.
Date Fri, 18 Aug 2017 21:19:39 GMT
Dang, sorry about that… The second I pushed it I realized this should’ve gone to the not yet existing 6_6_1…

Should I revert it from this branch?

> On 18 Aug 2017, at 23:17, ab@apache.org wrote:
> 
> Repository: lucene-solr
> Updated Branches:
>  refs/heads/branch_6_6 c7f9fcea4 -> 54469c7ca
> 
> 
> SOLR-11221: SolrJmxReporter broken on core reload.
> 
> 
> Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
> Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/54469c7c
> Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/54469c7c
> Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/54469c7c
> 
> Branch: refs/heads/branch_6_6
> Commit: 54469c7ca5f639a8120d9e4b9e51c0f82ab57b9b
> Parents: c7f9fce
> Author: Andrzej Bialecki <ab@apache.org>
> Authored: Mon Aug 14 13:46:14 2017 +0200
> Committer: Andrzej Bialecki <ab@apache.org>
> Committed: Fri Aug 18 23:17:23 2017 +0200
> 
> ----------------------------------------------------------------------
> solr/CHANGES.txt                                |   3 +
> .../org/apache/solr/core/JmxMonitoredMap.java   |   2 +-
> .../org/apache/solr/metrics/MetricsMap.java     |  15 +-
> .../metrics/reporters/JmxObjectNameFactory.java | 158 ----
> .../solr/metrics/reporters/SolrJmxReporter.java |  26 +-
> .../reporters/jmx/JmxMetricsReporter.java       | 750 +++++++++++++++++++
> .../reporters/jmx/JmxObjectNameFactory.java     | 158 ++++
> .../metrics/reporters/jmx/package-info.java     |  21 +
> .../reporters/SolrJmxReporterCloudTest.java     | 122 +++
> 9 files changed, 1089 insertions(+), 166 deletions(-)
> ----------------------------------------------------------------------
> 
> 
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/54469c7c/solr/CHANGES.txt
> ----------------------------------------------------------------------
> diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
> index ec39853..25081ed 100644
> --- a/solr/CHANGES.txt
> +++ b/solr/CHANGES.txt
> @@ -58,6 +58,9 @@ Bug Fixes
> 
> * SOLR-11069: CDCR bootstrapping can get into an infinite loop when a core is reloaded (Amrit Sarkar, Erick Erickson)
> 
> +* SOLR-11221: SolrJmxReporter broken on core reload. This resulted in some or most metrics not being reported
> +  via JMX after core reloads, depending on timing. (ab)
> +
> ==================  6.6.0 ==================
> 
> Consult the LUCENE_CHANGES.txt file for additional, low level, changes in this release.
> 
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/54469c7c/solr/core/src/java/org/apache/solr/core/JmxMonitoredMap.java
> ----------------------------------------------------------------------
> diff --git a/solr/core/src/java/org/apache/solr/core/JmxMonitoredMap.java b/solr/core/src/java/org/apache/solr/core/JmxMonitoredMap.java
> index 3089674..199b141 100644
> --- a/solr/core/src/java/org/apache/solr/core/JmxMonitoredMap.java
> +++ b/solr/core/src/java/org/apache/solr/core/JmxMonitoredMap.java
> @@ -53,7 +53,7 @@ import org.apache.solr.common.SolrException;
> import org.apache.solr.common.util.NamedList;
> import org.apache.solr.core.SolrConfig.JmxConfiguration;
> import org.apache.solr.metrics.SolrCoreMetricManager;
> -import org.apache.solr.metrics.reporters.JmxObjectNameFactory;
> +import org.apache.solr.metrics.reporters.jmx.JmxObjectNameFactory;
> import org.slf4j.Logger;
> import org.slf4j.LoggerFactory;
> 
> 
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/54469c7c/solr/core/src/java/org/apache/solr/metrics/MetricsMap.java
> ----------------------------------------------------------------------
> diff --git a/solr/core/src/java/org/apache/solr/metrics/MetricsMap.java b/solr/core/src/java/org/apache/solr/metrics/MetricsMap.java
> index f43c60b..8e94712 100644
> --- a/solr/core/src/java/org/apache/solr/metrics/MetricsMap.java
> +++ b/solr/core/src/java/org/apache/solr/metrics/MetricsMap.java
> @@ -59,6 +59,7 @@ public class MetricsMap implements Gauge<Map<String,Object>>, DynamicMBean {
>   private final boolean useCachedStatsBetweenGetMBeanInfoCalls = Boolean.getBoolean("useCachedStatsBetweenGetMBeanInfoCalls");
> 
>   private BiConsumer<Boolean, Map<String, Object>> initializer;
> +  private Map<String, String> jmxAttributes = new HashMap<>();
>   private volatile Map<String,Object> cachedValue;
> 
>   public MetricsMap(BiConsumer<Boolean, Map<String,Object>> initializer) {
> @@ -83,6 +84,11 @@ public class MetricsMap implements Gauge<Map<String,Object>>, DynamicMBean {
>   @Override
>   public Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException {
>     Object val;
> +    // jmxAttributes override any real values
> +    val = jmxAttributes.get(attribute);
> +    if (val != null) {
> +      return val;
> +    }
>     Map<String,Object> stats = null;
>     if (useCachedStatsBetweenGetMBeanInfoCalls) {
>       Map<String,Object> cachedStats = this.cachedValue;
> @@ -111,7 +117,7 @@ public class MetricsMap implements Gauge<Map<String,Object>>, DynamicMBean {
> 
>   @Override
>   public void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
> -    throw new UnsupportedOperationException("Operation not Supported");
> +    jmxAttributes.put(attribute.getName(), String.valueOf(attribute.getValue()));
>   }
> 
>   @Override
> @@ -144,8 +150,15 @@ public class MetricsMap implements Gauge<Map<String,Object>>, DynamicMBean {
>     if (useCachedStatsBetweenGetMBeanInfoCalls) {
>       cachedValue = stats;
>     }
> +    jmxAttributes.forEach((k, v) -> {
> +      attrInfoList.add(new MBeanAttributeInfo(k, String.class.getName(),
> +          null, true, false, false));
> +    });
>     try {
>       stats.forEach((k, v) -> {
> +        if (jmxAttributes.containsKey(k)) {
> +          return;
> +        }
>         Class type = v.getClass();
>         OpenType typeBox = determineType(type);
>         if (type.equals(String.class) || typeBox == null) {
> 
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/54469c7c/solr/core/src/java/org/apache/solr/metrics/reporters/JmxObjectNameFactory.java
> ----------------------------------------------------------------------
> diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/JmxObjectNameFactory.java b/solr/core/src/java/org/apache/solr/metrics/reporters/JmxObjectNameFactory.java
> deleted file mode 100644
> index c9c9439..0000000
> --- a/solr/core/src/java/org/apache/solr/metrics/reporters/JmxObjectNameFactory.java
> +++ /dev/null
> @@ -1,158 +0,0 @@
> -/*
> - * 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.reporters;
> -
> -import javax.management.MalformedObjectNameException;
> -import javax.management.ObjectName;
> -
> -import java.util.Arrays;
> -
> -import com.codahale.metrics.ObjectNameFactory;
> -import org.apache.solr.metrics.SolrMetricInfo;
> -
> -/**
> - * Factory to create MBean names for a given metric.
> - */
> -public class JmxObjectNameFactory implements ObjectNameFactory {
> -
> -  private final String domain;
> -  private final String[] subdomains;
> -  private final String reporterName;
> -  private final String[] props;
> -
> -  /**
> -   * Create ObjectName factory.
> -   * @param reporterName name of the reporter
> -   * @param domain JMX domain name
> -   * @param additionalProperties additional properties as key, value pairs.
> -   */
> -  public JmxObjectNameFactory(String reporterName, String domain, String... additionalProperties) {
> -    this.reporterName = reporterName.replaceAll(":", "_");
> -    this.domain = domain;
> -    this.subdomains = domain.replaceAll(":", "_").split("\\.");
> -    if (additionalProperties != null && (additionalProperties.length % 2) != 0) {
> -      throw new IllegalArgumentException("additionalProperties length must be even: " + Arrays.toString(additionalProperties));
> -    }
> -    this.props = additionalProperties;
> -  }
> -
> -  /**
> -   * Create a hierarchical name.
> -   *
> -   * @param type    metric class, eg. "counters", may be null for non-metric MBeans
> -   * @param currentDomain  JMX domain
> -   * @param name    object name
> -   */
> -  @Override
> -  public ObjectName createName(String type, String currentDomain, String name) {
> -    SolrMetricInfo metricInfo = SolrMetricInfo.of(name);
> -    String safeName = metricInfo != null ? metricInfo.name : name;
> -    safeName = safeName.replaceAll(":", "_");
> -    // It turns out that ObjectName(String) mostly preserves key ordering
> -    // as specified in the constructor (except for the 'type' key that ends
> -    // up at top level) - unlike ObjectName(String, Map) constructor
> -    // that seems to have a mind of its own...
> -    StringBuilder sb = new StringBuilder();
> -    if (domain.equals(currentDomain)) {
> -      if (subdomains != null && subdomains.length > 1) {
> -        // use only first segment as domain
> -        sb.append(subdomains[0]);
> -        sb.append(':');
> -        // use remaining segments as properties
> -        for (int i = 1; i < subdomains.length; i++) {
> -          if (i > 1) {
> -            sb.append(',');
> -          }
> -          sb.append("dom");
> -          sb.append(String.valueOf(i));
> -          sb.append('=');
> -          sb.append(subdomains[i]);
> -        }
> -        sb.append(','); // separate from other properties
> -      } else {
> -        sb.append(currentDomain.replaceAll(":", "_"));
> -        sb.append(':');
> -      }
> -    } else {
> -      sb.append(currentDomain);
> -      sb.append(':');
> -    }
> -    sb.append("reporter=");
> -    sb.append(reporterName);
> -    sb.append(',');
> -    if (metricInfo != null) {
> -      sb.append("category=");
> -      sb.append(metricInfo.category.toString());
> -      if (metricInfo.scope != null) {
> -        sb.append(",scope=");
> -        sb.append(metricInfo.scope);
> -      }
> -      // we could also split by type, but don't call it 'type' :)
> -      // if (type != null) {
> -      //   sb.append(",class=");
> -      //   sb.append(type);
> -      // }
> -      sb.append(",name=");
> -      sb.append(safeName);
> -    } else {
> -      // make dotted names into hierarchies
> -      String[] path = safeName.split("\\.");
> -      for (int i = 0; i < path.length - 1; i++) {
> -        if (i > 0) {
> -          sb.append(',');
> -        }
> -        sb.append("name"); sb.append(String.valueOf(i));
> -        sb.append('=');
> -        sb.append(path[i]);
> -      }
> -      if (path.length > 1) {
> -        sb.append(',');
> -      }
> -      // split by type
> -      // if (type != null) {
> -      //   sb.append("class=");
> -      //   sb.append(type);
> -      // }
> -      sb.append("name=");
> -      sb.append(path[path.length - 1]);
> -    }
> -    if (props != null && props.length > 0) {
> -      for (int i = 0; i < props.length; i += 2) {
> -        if (props[i] == null || props[i].isEmpty()) {
> -          continue;
> -        }
> -        if (props[i + 1] == null || props[i + 1].isEmpty()) {
> -          continue;
> -        }
> -        sb.append(',');
> -        sb.append(props[i]);
> -        sb.append('=');
> -        sb.append(props[i + 1]);
> -      }
> -    }
> -
> -    ObjectName objectName;
> -
> -    try {
> -      objectName = new ObjectName(sb.toString());
> -    } catch (MalformedObjectNameException e) {
> -      throw new RuntimeException(sb.toString(), e);
> -    }
> -
> -    return objectName;
> -  }
> -}
> 
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/54469c7c/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
> ----------------------------------------------------------------------
> diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
> index e6ce124..183e057 100644
> --- a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
> +++ b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
> @@ -27,8 +27,11 @@ import com.codahale.metrics.JmxReporter;
> import com.codahale.metrics.MetricFilter;
> import com.codahale.metrics.MetricRegistry;
> import org.apache.solr.core.PluginInfo;
> +
> import org.apache.solr.metrics.SolrMetricManager;
> import org.apache.solr.metrics.SolrMetricReporter;
> +import org.apache.solr.metrics.reporters.jmx.JmxMetricsReporter;
> +import org.apache.solr.metrics.reporters.jmx.JmxObjectNameFactory;
> import org.apache.solr.util.JmxUtil;
> import org.slf4j.Logger;
> import org.slf4j.LoggerFactory;
> @@ -51,9 +54,10 @@ public class SolrJmxReporter extends SolrMetricReporter {
>   private String rootName;
>   private List<String> filters = new ArrayList<>();
> 
> -  private JmxReporter reporter;
>   private MetricRegistry registry;
>   private MBeanServer mBeanServer;
> +  private JmxMetricsReporter reporter;
> +  private boolean started;
> 
>   /**
>    * Creates a new instance of {@link SolrJmxReporter}.
> @@ -111,18 +115,19 @@ public class SolrJmxReporter extends SolrMetricReporter {
>     if (filters.isEmpty()) {
>       filter = MetricFilter.ALL;
>     } else {
> -      // apply also prefix filters
>       filter = new SolrMetricManager.PrefixFilter(filters);
>     }
> 
> -    reporter = JmxReporter.forRegistry(registry)
> +    String tag = Integer.toHexString(this.hashCode());
> +    reporter = JmxMetricsReporter.forRegistry(registry)
>                           .registerWith(mBeanServer)
>                           .inDomain(fullDomain)
>                           .filter(filter)
>                           .createsObjectNamesWith(jmxObjectNameFactory)
> +                          .withTag(tag)
>                           .build();
>     reporter.start();
> -
> +    started = true;
>     log.info("JMX monitoring for '" + fullDomain + "' (registry '" + registryName + "') enabled at server: " + mBeanServer);
>   }
> 
> @@ -131,6 +136,8 @@ public class SolrJmxReporter extends SolrMetricReporter {
>    */
>   @Override
>   public synchronized void close() {
> +    log.info("Closing reporter " + this + " for registry " + registryName + " / " + registry);
> +    started = false;
>     if (reporter != null) {
>       reporter.close();
>       reporter = null;
> @@ -242,16 +249,23 @@ public class SolrJmxReporter extends SolrMetricReporter {
> 
>   /**
>    * For unit tests.
> -   * @return true if this reporter is actively reporting metrics to JMX.
> +   * @return true if this reporter is going to report metrics to JMX.
>    */
>   public boolean isActive() {
>     return reporter != null;
>   }
> 
> +  /**
> +   * For unit tests.
> +   * @return true if this reporter has been started and is reporting metrics to JMX.
> +   */
> +  public boolean isStarted() {
> +    return started;
> +  }
> +
>   @Override
>   public String toString() {
>     return String.format(Locale.ENGLISH, "[%s@%s: rootName = %s, domain = %s, service url = %s, agent id = %s]",
>         getClass().getName(), Integer.toHexString(hashCode()), rootName, domain, serviceUrl, agentId);
>   }
> -
> }
> 
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/54469c7c/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxMetricsReporter.java
> ----------------------------------------------------------------------
> diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxMetricsReporter.java b/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxMetricsReporter.java
> new file mode 100644
> index 0000000..54da5fa
> --- /dev/null
> +++ b/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxMetricsReporter.java
> @@ -0,0 +1,750 @@
> +/*
> + * 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.reporters.jmx;
> +
> +import javax.management.Attribute;
> +import javax.management.InstanceAlreadyExistsException;
> +import javax.management.InstanceNotFoundException;
> +import javax.management.JMException;
> +import javax.management.MBeanRegistrationException;
> +import javax.management.MBeanServer;
> +import javax.management.ObjectInstance;
> +import javax.management.ObjectName;
> +import javax.management.Query;
> +import javax.management.QueryExp;
> +import java.io.Closeable;
> +import java.lang.invoke.MethodHandles;
> +import java.lang.management.ManagementFactory;
> +import java.util.HashMap;
> +import java.util.Locale;
> +import java.util.Map;
> +import java.util.Set;
> +import java.util.concurrent.ConcurrentHashMap;
> +import java.util.concurrent.TimeUnit;
> +
> +import com.codahale.metrics.Counter;
> +import com.codahale.metrics.DefaultObjectNameFactory;
> +import com.codahale.metrics.Gauge;
> +import com.codahale.metrics.Histogram;
> +import com.codahale.metrics.Meter;
> +import com.codahale.metrics.Metered;
> +import com.codahale.metrics.Metric;
> +import com.codahale.metrics.MetricFilter;
> +import com.codahale.metrics.MetricRegistry;
> +import com.codahale.metrics.MetricRegistryListener;
> +import com.codahale.metrics.ObjectNameFactory;
> +import com.codahale.metrics.Reporter;
> +import com.codahale.metrics.Timer;
> +import org.apache.solr.metrics.MetricsMap;
> +import org.slf4j.Logger;
> +import org.slf4j.LoggerFactory;
> +
> +/**
> + * This is a modified copy of Dropwizard's {@link com.codahale.metrics.JmxReporter} and classes that it internally uses,
> + * with a few important differences:
> + * <ul>
> + * <li>this class knows that it can directly use {@link MetricsMap} as a dynamic MBean.</li>
> + * <li>this class allows us to "tag" MBean instances so that we can later unregister only instances registered with the
> + * same tag.</li>
> + * <li>this class processes all metrics already existing in the registry at the time when reporter is started.</li>
> + * </ul>
> + */
> +public class JmxMetricsReporter implements Reporter, Closeable {
> +  private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
> +
> +  public static final String INSTANCE_TAG = "_instanceTag";
> +
> +  public static Builder forRegistry(MetricRegistry registry) {
> +    return new Builder(registry);
> +  }
> +
> +  public static class Builder {
> +    private final MetricRegistry registry;
> +    private MBeanServer mBeanServer;
> +    private TimeUnit rateUnit;
> +    private TimeUnit durationUnit;
> +    private ObjectNameFactory objectNameFactory;
> +    private MetricFilter filter = MetricFilter.ALL;
> +    private String domain;
> +    private String tag;
> +
> +    private Builder(MetricRegistry registry) {
> +      this.registry = registry;
> +      this.rateUnit = TimeUnit.SECONDS;
> +      this.durationUnit = TimeUnit.MILLISECONDS;
> +      this.domain = "metrics";
> +      this.objectNameFactory = new DefaultObjectNameFactory();
> +    }
> +
> +    /**
> +     * Register MBeans with the given {@link MBeanServer}.
> +     *
> +     * @param mBeanServer     an {@link MBeanServer}
> +     * @return {@code this}
> +     */
> +    public Builder registerWith(MBeanServer mBeanServer) {
> +      this.mBeanServer = mBeanServer;
> +      return this;
> +    }
> +
> +    /**
> +     * Convert rates to the given time unit.
> +     *
> +     * @param rateUnit a unit of time
> +     * @return {@code this}
> +     */
> +    public Builder convertRatesTo(TimeUnit rateUnit) {
> +      this.rateUnit = rateUnit;
> +      return this;
> +    }
> +
> +    public Builder createsObjectNamesWith(ObjectNameFactory onFactory) {
> +      if(onFactory == null) {
> +        throw new IllegalArgumentException("null objectNameFactory");
> +      }
> +      this.objectNameFactory = onFactory;
> +      return this;
> +    }
> +
> +    /**
> +     * Convert durations to the given time unit.
> +     *
> +     * @param durationUnit a unit of time
> +     * @return {@code this}
> +     */
> +    public Builder convertDurationsTo(TimeUnit durationUnit) {
> +      this.durationUnit = durationUnit;
> +      return this;
> +    }
> +
> +    /**
> +     * Only report metrics which match the given filter.
> +     *
> +     * @param filter a {@link MetricFilter}
> +     * @return {@code this}
> +     */
> +    public Builder filter(MetricFilter filter) {
> +      this.filter = filter;
> +      return this;
> +    }
> +
> +    public Builder inDomain(String domain) {
> +      this.domain = domain;
> +      return this;
> +    }
> +
> +    public Builder withTag(String tag) {
> +      this.tag = tag;
> +      return this;
> +    }
> +
> +    public JmxMetricsReporter build() {
> +      if (mBeanServer == null) {
> +        mBeanServer = ManagementFactory.getPlatformMBeanServer();
> +      }
> +      if (tag == null) {
> +        tag = Integer.toHexString(this.hashCode());
> +      }
> +      return new JmxMetricsReporter(mBeanServer, domain, registry, filter, rateUnit, durationUnit, objectNameFactory, tag);
> +    }
> +
> +  }
> +
> +  // MBean interfaces and base classes
> +  public interface MetricMBean {
> +    ObjectName objectName();
> +    // this strange-looking method name is used for producing "_instanceTag" attribute name
> +    String get_instanceTag();
> +  }
> +
> +
> +  private abstract static class AbstractBean implements MetricMBean {
> +    private final ObjectName objectName;
> +    private final String instanceTag;
> +
> +    AbstractBean(ObjectName objectName, String instanceTag) {
> +      this.objectName = objectName;
> +      this.instanceTag = instanceTag;
> +    }
> +
> +    @Override
> +    public String get_instanceTag() {
> +      return instanceTag;
> +    }
> +
> +    @Override
> +    public ObjectName objectName() {
> +      return objectName;
> +    }
> +  }
> +
> +  public interface JmxGaugeMBean extends MetricMBean {
> +    Object getValue();
> +  }
> +
> +  private static class JmxGauge extends AbstractBean implements JmxGaugeMBean {
> +    private final Gauge<?> metric;
> +
> +    private JmxGauge(Gauge<?> metric, ObjectName objectName, String tag) {
> +      super(objectName, tag);
> +      this.metric = metric;
> +    }
> +
> +    @Override
> +    public Object getValue() {
> +      return metric.getValue();
> +    }
> +  }
> +
> +  public interface JmxCounterMBean extends MetricMBean {
> +    long getCount();
> +  }
> +
> +  private static class JmxCounter extends AbstractBean implements JmxCounterMBean {
> +    private final Counter metric;
> +
> +    private JmxCounter(Counter metric, ObjectName objectName, String tag) {
> +      super(objectName, tag);
> +      this.metric = metric;
> +    }
> +
> +    @Override
> +    public long getCount() {
> +      return metric.getCount();
> +    }
> +  }
> +
> +  public interface JmxHistogramMBean extends MetricMBean {
> +    long getCount();
> +
> +    long getMin();
> +
> +    long getMax();
> +
> +    double getMean();
> +
> +    double getStdDev();
> +
> +    double get50thPercentile();
> +
> +    double get75thPercentile();
> +
> +    double get95thPercentile();
> +
> +    double get98thPercentile();
> +
> +    double get99thPercentile();
> +
> +    double get999thPercentile();
> +
> +    long[] values();
> +
> +    long getSnapshotSize();
> +  }
> +
> +  private static class JmxHistogram extends AbstractBean implements JmxHistogramMBean {
> +    private final Histogram metric;
> +
> +    private JmxHistogram(Histogram metric, ObjectName objectName, String tag) {
> +      super(objectName, tag);
> +      this.metric = metric;
> +    }
> +
> +    @Override
> +    public double get50thPercentile() {
> +      return metric.getSnapshot().getMedian();
> +    }
> +
> +    @Override
> +    public long getCount() {
> +      return metric.getCount();
> +    }
> +
> +    @Override
> +    public long getMin() {
> +      return metric.getSnapshot().getMin();
> +    }
> +
> +    @Override
> +    public long getMax() {
> +      return metric.getSnapshot().getMax();
> +    }
> +
> +    @Override
> +    public double getMean() {
> +      return metric.getSnapshot().getMean();
> +    }
> +
> +    @Override
> +    public double getStdDev() {
> +      return metric.getSnapshot().getStdDev();
> +    }
> +
> +    @Override
> +    public double get75thPercentile() {
> +      return metric.getSnapshot().get75thPercentile();
> +    }
> +
> +    @Override
> +    public double get95thPercentile() {
> +      return metric.getSnapshot().get95thPercentile();
> +    }
> +
> +    @Override
> +    public double get98thPercentile() {
> +      return metric.getSnapshot().get98thPercentile();
> +    }
> +
> +    @Override
> +    public double get99thPercentile() {
> +      return metric.getSnapshot().get99thPercentile();
> +    }
> +
> +    @Override
> +    public double get999thPercentile() {
> +      return metric.getSnapshot().get999thPercentile();
> +    }
> +
> +    @Override
> +    public long[] values() {
> +      return metric.getSnapshot().getValues();
> +    }
> +
> +    public long getSnapshotSize() {
> +      return metric.getSnapshot().size();
> +    }
> +  }
> +
> +  public interface JmxMeterMBean extends MetricMBean {
> +    long getCount();
> +
> +    double getMeanRate();
> +
> +    double getOneMinuteRate();
> +
> +    double getFiveMinuteRate();
> +
> +    double getFifteenMinuteRate();
> +
> +    String getRateUnit();
> +  }
> +
> +  private static class JmxMeter extends AbstractBean implements JmxMeterMBean {
> +    private final Metered metric;
> +    private final double rateFactor;
> +    private final String rateUnit;
> +
> +    private JmxMeter(Metered metric, ObjectName objectName, TimeUnit rateUnit, String tag) {
> +      super(objectName, tag);
> +      this.metric = metric;
> +      this.rateFactor = rateUnit.toSeconds(1);
> +      this.rateUnit = ("events/" + calculateRateUnit(rateUnit)).intern();
> +    }
> +
> +    @Override
> +    public long getCount() {
> +      return metric.getCount();
> +    }
> +
> +    @Override
> +    public double getMeanRate() {
> +      return metric.getMeanRate() * rateFactor;
> +    }
> +
> +    @Override
> +    public double getOneMinuteRate() {
> +      return metric.getOneMinuteRate() * rateFactor;
> +    }
> +
> +    @Override
> +    public double getFiveMinuteRate() {
> +      return metric.getFiveMinuteRate() * rateFactor;
> +    }
> +
> +    @Override
> +    public double getFifteenMinuteRate() {
> +      return metric.getFifteenMinuteRate() * rateFactor;
> +    }
> +
> +    @Override
> +    public String getRateUnit() {
> +      return rateUnit;
> +    }
> +
> +    private String calculateRateUnit(TimeUnit unit) {
> +      final String s = unit.toString().toLowerCase(Locale.US);
> +      return s.substring(0, s.length() - 1);
> +    }
> +  }
> +
> +  public interface JmxTimerMBean extends JmxMeterMBean {
> +    double getMin();
> +
> +    double getMax();
> +
> +    double getMean();
> +
> +    double getStdDev();
> +
> +    double get50thPercentile();
> +
> +    double get75thPercentile();
> +
> +    double get95thPercentile();
> +
> +    double get98thPercentile();
> +
> +    double get99thPercentile();
> +
> +    double get999thPercentile();
> +
> +    long[] values();
> +    String getDurationUnit();
> +  }
> +
> +  private static class JmxTimer extends JmxMeter implements JmxTimerMBean {
> +    private final Timer metric;
> +    private final double durationFactor;
> +    private final String durationUnit;
> +
> +    private JmxTimer(Timer metric,
> +                     ObjectName objectName,
> +                     TimeUnit rateUnit,
> +                     TimeUnit durationUnit, String tag) {
> +      super(metric, objectName, rateUnit, tag);
> +      this.metric = metric;
> +      this.durationFactor = 1.0 / durationUnit.toNanos(1);
> +      this.durationUnit = durationUnit.toString().toLowerCase(Locale.US);
> +    }
> +
> +    @Override
> +    public double get50thPercentile() {
> +      return metric.getSnapshot().getMedian() * durationFactor;
> +    }
> +
> +    @Override
> +    public double getMin() {
> +      return metric.getSnapshot().getMin() * durationFactor;
> +    }
> +
> +    @Override
> +    public double getMax() {
> +      return metric.getSnapshot().getMax() * durationFactor;
> +    }
> +
> +    @Override
> +    public double getMean() {
> +      return metric.getSnapshot().getMean() * durationFactor;
> +    }
> +
> +    @Override
> +    public double getStdDev() {
> +      return metric.getSnapshot().getStdDev() * durationFactor;
> +    }
> +
> +    @Override
> +    public double get75thPercentile() {
> +      return metric.getSnapshot().get75thPercentile() * durationFactor;
> +    }
> +
> +    @Override
> +    public double get95thPercentile() {
> +      return metric.getSnapshot().get95thPercentile() * durationFactor;
> +    }
> +
> +    @Override
> +    public double get98thPercentile() {
> +      return metric.getSnapshot().get98thPercentile() * durationFactor;
> +    }
> +
> +    @Override
> +    public double get99thPercentile() {
> +      return metric.getSnapshot().get99thPercentile() * durationFactor;
> +    }
> +
> +    @Override
> +    public double get999thPercentile() {
> +      return metric.getSnapshot().get999thPercentile() * durationFactor;
> +    }
> +
> +    @Override
> +    public long[] values() {
> +      return metric.getSnapshot().getValues();
> +    }
> +
> +    @Override
> +    public String getDurationUnit() {
> +      return durationUnit;
> +    }
> +  }
> +
> +  private static class JmxListener implements MetricRegistryListener {
> +
> +    private final String name;
> +    private final MBeanServer mBeanServer;
> +    private final MetricFilter filter;
> +    private final TimeUnit rateUnit;
> +    private final TimeUnit durationUnit;
> +    private final Map<ObjectName, ObjectName> registered;
> +    private final ObjectNameFactory objectNameFactory;
> +    private final String tag;
> +    private final QueryExp exp;
> +
> +    private JmxListener(MBeanServer mBeanServer, String name, MetricFilter filter, TimeUnit rateUnit, TimeUnit durationUnit,
> +                        ObjectNameFactory objectNameFactory, String tag) {
> +      this.mBeanServer = mBeanServer;
> +      this.name = name;
> +      this.filter = filter;
> +      this.rateUnit = rateUnit;
> +      this.durationUnit = durationUnit;
> +      this.registered = new ConcurrentHashMap<>();
> +      this.objectNameFactory = objectNameFactory;
> +      this.tag = tag;
> +      this.exp = Query.eq(Query.attr(INSTANCE_TAG), Query.value(tag));
> +    }
> +
> +    private void registerMBean(Object mBean, ObjectName objectName) throws InstanceAlreadyExistsException, JMException {
> +      // remove previous bean if exists
> +      if (mBeanServer.isRegistered(objectName)) {
> +        if (LOG.isDebugEnabled()) {
> +          Set<ObjectInstance> objects = mBeanServer.queryMBeans(objectName, null);
> +          LOG.debug("## removing existing " + objects.size() + " bean(s) for " + objectName.getCanonicalName() + ", current tag=" + tag + ":");
> +          for (ObjectInstance inst : objects) {
> +            LOG.debug("## - tag=" + mBeanServer.getAttribute(inst.getObjectName(), INSTANCE_TAG));
> +          }
> +        }
> +        mBeanServer.unregisterMBean(objectName);
> +      }
> +      ObjectInstance objectInstance = mBeanServer.registerMBean(mBean, objectName);
> +      if (objectInstance != null) {
> +        // the websphere mbeanserver rewrites the objectname to include
> +        // cell, node & server info
> +        // make sure we capture the new objectName for unregistration
> +        registered.put(objectName, objectInstance.getObjectName());
> +      } else {
> +        registered.put(objectName, objectName);
> +      }
> +      LOG.debug("## registered " + objectInstance.getObjectName().getCanonicalName() + ", tag=" + tag);
> +    }
> +
> +    private void unregisterMBean(ObjectName originalObjectName) throws InstanceNotFoundException, MBeanRegistrationException {
> +      ObjectName objectName = registered.remove(originalObjectName);
> +      if (objectName == null) {
> +        objectName = originalObjectName;
> +      }
> +      Set<ObjectInstance> objects = mBeanServer.queryMBeans(objectName, exp);
> +      for (ObjectInstance o : objects) {
> +        LOG.debug("## Unregistered " + o.getObjectName().getCanonicalName() + ", tag=" + tag);
> +        mBeanServer.unregisterMBean(o.getObjectName());
> +      }
> +    }
> +
> +    @Override
> +    public void onGaugeAdded(String name, Gauge<?> gauge) {
> +      try {
> +        if (filter.matches(name, gauge)) {
> +          final ObjectName objectName = createName("gauges", name);
> +          if (gauge instanceof MetricsMap) {
> +            ((MetricsMap)gauge).setAttribute(new Attribute(INSTANCE_TAG, tag));
> +            registerMBean(gauge, objectName);
> +          } else {
> +            registerMBean(new JmxGauge(gauge, objectName, tag), objectName);
> +          }
> +        }
> +      } catch (InstanceAlreadyExistsException e) {
> +        LOG.debug("Unable to register gauge", e);
> +      } catch (JMException e) {
> +        LOG.warn("Unable to register gauge", e);
> +      }
> +    }
> +
> +    @Override
> +    public void onGaugeRemoved(String name) {
> +      try {
> +        final ObjectName objectName = createName("gauges", name);
> +        unregisterMBean(objectName);
> +      } catch (InstanceNotFoundException e) {
> +        LOG.debug("Unable to unregister gauge", e);
> +      } catch (MBeanRegistrationException e) {
> +        LOG.warn("Unable to unregister gauge", e);
> +      }
> +    }
> +
> +    @Override
> +    public void onCounterAdded(String name, Counter counter) {
> +      try {
> +        if (filter.matches(name, counter)) {
> +          final ObjectName objectName = createName("counters", name);
> +          registerMBean(new JmxCounter(counter, objectName, tag), objectName);
> +        }
> +      } catch (InstanceAlreadyExistsException e) {
> +        LOG.debug("Unable to register counter", e);
> +      } catch (JMException e) {
> +        LOG.warn("Unable to register counter", e);
> +      }
> +    }
> +
> +    @Override
> +    public void onCounterRemoved(String name) {
> +      try {
> +        final ObjectName objectName = createName("counters", name);
> +        unregisterMBean(objectName);
> +      } catch (InstanceNotFoundException e) {
> +        LOG.debug("Unable to unregister counter", e);
> +      } catch (MBeanRegistrationException e) {
> +        LOG.warn("Unable to unregister counter", e);
> +      }
> +    }
> +
> +    @Override
> +    public void onHistogramAdded(String name, Histogram histogram) {
> +      try {
> +        if (filter.matches(name, histogram)) {
> +          final ObjectName objectName = createName("histograms", name);
> +          registerMBean(new JmxHistogram(histogram, objectName, tag), objectName);
> +        }
> +      } catch (InstanceAlreadyExistsException e) {
> +        LOG.debug("Unable to register histogram", e);
> +      } catch (JMException e) {
> +        LOG.warn("Unable to register histogram", e);
> +      }
> +    }
> +
> +    @Override
> +    public void onHistogramRemoved(String name) {
> +      try {
> +        final ObjectName objectName = createName("histograms", name);
> +        unregisterMBean(objectName);
> +      } catch (InstanceNotFoundException e) {
> +        LOG.debug("Unable to unregister histogram", e);
> +      } catch (MBeanRegistrationException e) {
> +        LOG.warn("Unable to unregister histogram", e);
> +      }
> +    }
> +
> +    @Override
> +    public void onMeterAdded(String name, Meter meter) {
> +      try {
> +        if (filter.matches(name, meter)) {
> +          final ObjectName objectName = createName("meters", name);
> +          registerMBean(new JmxMeter(meter, objectName, rateUnit, tag), objectName);
> +        }
> +      } catch (InstanceAlreadyExistsException e) {
> +        LOG.debug("Unable to register meter", e);
> +      } catch (JMException e) {
> +        LOG.warn("Unable to register meter", e);
> +      }
> +    }
> +
> +    @Override
> +    public void onMeterRemoved(String name) {
> +      try {
> +        final ObjectName objectName = createName("meters", name);
> +        unregisterMBean(objectName);
> +      } catch (InstanceNotFoundException e) {
> +        LOG.debug("Unable to unregister meter", e);
> +      } catch (MBeanRegistrationException e) {
> +        LOG.warn("Unable to unregister meter", e);
> +      }
> +    }
> +
> +    @Override
> +    public void onTimerAdded(String name, Timer timer) {
> +      try {
> +        if (filter.matches(name, timer)) {
> +          final ObjectName objectName = createName("timers", name);
> +          registerMBean(new JmxTimer(timer, objectName, rateUnit, durationUnit, tag), objectName);
> +        }
> +      } catch (InstanceAlreadyExistsException e) {
> +        LOG.debug("Unable to register timer", e);
> +      } catch (JMException e) {
> +        LOG.warn("Unable to register timer", e);
> +      }
> +    }
> +
> +    @Override
> +    public void onTimerRemoved(String name) {
> +      try {
> +        final ObjectName objectName = createName("timers", name);
> +        unregisterMBean(objectName);
> +      } catch (InstanceNotFoundException e) {
> +        LOG.debug("Unable to unregister timer", e);
> +      } catch (MBeanRegistrationException e) {
> +        LOG.warn("Unable to unregister timer", e);
> +      }
> +    }
> +
> +    private ObjectName createName(String type, String name) {
> +      return objectNameFactory.createName(type, this.name, name);
> +    }
> +
> +    void unregisterAll() {
> +      for (ObjectName name : registered.keySet()) {
> +        try {
> +          unregisterMBean(name);
> +        } catch (InstanceNotFoundException e) {
> +          LOG.debug("Unable to unregister metric", e);
> +        } catch (MBeanRegistrationException e) {
> +          LOG.warn("Unable to unregister metric", e);
> +        }
> +      }
> +      registered.clear();
> +    }
> +  }
> +
> +  private final MetricRegistry registry;
> +  private final JmxListener listener;
> +
> +  private JmxMetricsReporter(MBeanServer mBeanServer,
> +                             String domain,
> +                             MetricRegistry registry,
> +                             MetricFilter filter,
> +                             TimeUnit rateUnit,
> +                             TimeUnit durationUnit,
> +                             ObjectNameFactory objectNameFactory,
> +                             String tag) {
> +    this.registry = registry;
> +    this.listener = new JmxListener(mBeanServer, domain, filter, rateUnit, durationUnit, objectNameFactory, tag);
> +  }
> +
> +  public void start() {
> +    registry.addListener(listener);
> +    // process existing metrics
> +    Map<String, Metric> metrics = new HashMap<>(registry.getMetrics());
> +    metrics.forEach((k, v) -> {
> +      if (v instanceof Counter) {
> +        listener.onCounterAdded(k, (Counter)v);
> +      } else if (v instanceof Meter) {
> +        listener.onMeterAdded(k, (Meter)v);
> +      } else if (v instanceof Histogram) {
> +        listener.onHistogramAdded(k, (Histogram)v);
> +      } else if (v instanceof Timer) {
> +        listener.onTimerAdded(k, (Timer)v);
> +      } else if (v instanceof Gauge) {
> +        listener.onGaugeAdded(k, (Gauge)v);
> +      } else {
> +        LOG.warn("Unknown metric type " + v.getClass().getName() + " for metric '" + k + "', ignoring");
> +      }
> +    });
> +  }
> +
> +  @Override
> +  public void close() {
> +    registry.removeListener(listener);
> +    listener.unregisterAll();
> +  }
> +}
> 
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/54469c7c/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxObjectNameFactory.java
> ----------------------------------------------------------------------
> diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxObjectNameFactory.java b/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxObjectNameFactory.java
> new file mode 100644
> index 0000000..def328b
> --- /dev/null
> +++ b/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/JmxObjectNameFactory.java
> @@ -0,0 +1,158 @@
> +/*
> + * 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.reporters.jmx;
> +
> +import javax.management.MalformedObjectNameException;
> +import javax.management.ObjectName;
> +
> +import java.util.Arrays;
> +
> +import com.codahale.metrics.ObjectNameFactory;
> +import org.apache.solr.metrics.SolrMetricInfo;
> +
> +/**
> + * Factory to create MBean names for a given metric.
> + */
> +public class JmxObjectNameFactory implements ObjectNameFactory {
> +
> +  private final String domain;
> +  private final String[] subdomains;
> +  private final String reporterName;
> +  private final String[] props;
> +
> +  /**
> +   * Create ObjectName factory.
> +   * @param reporterName name of the reporter
> +   * @param domain JMX domain name
> +   * @param additionalProperties additional properties as key, value pairs.
> +   */
> +  public JmxObjectNameFactory(String reporterName, String domain, String... additionalProperties) {
> +    this.reporterName = reporterName.replaceAll(":", "_");
> +    this.domain = domain;
> +    this.subdomains = domain.replaceAll(":", "_").split("\\.");
> +    if (additionalProperties != null && (additionalProperties.length % 2) != 0) {
> +      throw new IllegalArgumentException("additionalProperties length must be even: " + Arrays.toString(additionalProperties));
> +    }
> +    this.props = additionalProperties;
> +  }
> +
> +  /**
> +   * Create a hierarchical name.
> +   *
> +   * @param type    metric class, eg. "counters", may be null for non-metric MBeans
> +   * @param currentDomain  JMX domain
> +   * @param name    object name
> +   */
> +  @Override
> +  public ObjectName createName(String type, String currentDomain, String name) {
> +    SolrMetricInfo metricInfo = SolrMetricInfo.of(name);
> +    String safeName = metricInfo != null ? metricInfo.name : name;
> +    safeName = safeName.replaceAll(":", "_");
> +    // It turns out that ObjectName(String) mostly preserves key ordering
> +    // as specified in the constructor (except for the 'type' key that ends
> +    // up at top level) - unlike ObjectName(String, Map) constructor
> +    // that seems to have a mind of its own...
> +    StringBuilder sb = new StringBuilder();
> +    if (domain.equals(currentDomain)) {
> +      if (subdomains != null && subdomains.length > 1) {
> +        // use only first segment as domain
> +        sb.append(subdomains[0]);
> +        sb.append(':');
> +        // use remaining segments as properties
> +        for (int i = 1; i < subdomains.length; i++) {
> +          if (i > 1) {
> +            sb.append(',');
> +          }
> +          sb.append("dom");
> +          sb.append(String.valueOf(i));
> +          sb.append('=');
> +          sb.append(subdomains[i]);
> +        }
> +        sb.append(','); // separate from other properties
> +      } else {
> +        sb.append(currentDomain.replaceAll(":", "_"));
> +        sb.append(':');
> +      }
> +    } else {
> +      sb.append(currentDomain);
> +      sb.append(':');
> +    }
> +    sb.append("reporter=");
> +    sb.append(reporterName);
> +    sb.append(',');
> +    if (metricInfo != null) {
> +      sb.append("category=");
> +      sb.append(metricInfo.category.toString());
> +      if (metricInfo.scope != null) {
> +        sb.append(",scope=");
> +        sb.append(metricInfo.scope);
> +      }
> +      // we could also split by type, but don't call it 'type' :)
> +      // if (type != null) {
> +      //   sb.append(",class=");
> +      //   sb.append(type);
> +      // }
> +      sb.append(",name=");
> +      sb.append(safeName);
> +    } else {
> +      // make dotted names into hierarchies
> +      String[] path = safeName.split("\\.");
> +      for (int i = 0; i < path.length - 1; i++) {
> +        if (i > 0) {
> +          sb.append(',');
> +        }
> +        sb.append("name"); sb.append(String.valueOf(i));
> +        sb.append('=');
> +        sb.append(path[i]);
> +      }
> +      if (path.length > 1) {
> +        sb.append(',');
> +      }
> +      // split by type
> +      // if (type != null) {
> +      //   sb.append("class=");
> +      //   sb.append(type);
> +      // }
> +      sb.append("name=");
> +      sb.append(path[path.length - 1]);
> +    }
> +    if (props != null && props.length > 0) {
> +      for (int i = 0; i < props.length; i += 2) {
> +        if (props[i] == null || props[i].isEmpty()) {
> +          continue;
> +        }
> +        if (props[i + 1] == null || props[i + 1].isEmpty()) {
> +          continue;
> +        }
> +        sb.append(',');
> +        sb.append(props[i]);
> +        sb.append('=');
> +        sb.append(props[i + 1]);
> +      }
> +    }
> +
> +    ObjectName objectName;
> +
> +    try {
> +      objectName = new ObjectName(sb.toString());
> +    } catch (MalformedObjectNameException e) {
> +      throw new RuntimeException(sb.toString(), e);
> +    }
> +
> +    return objectName;
> +  }
> +}
> 
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/54469c7c/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/package-info.java
> ----------------------------------------------------------------------
> diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/package-info.java b/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/package-info.java
> new file mode 100644
> index 0000000..bd71e9c
> --- /dev/null
> +++ b/solr/core/src/java/org/apache/solr/metrics/reporters/jmx/package-info.java
> @@ -0,0 +1,21 @@
> +/*
> + * 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.
> + */
> +
> +/**
> + * This package contains components that support {@link org.apache.solr.metrics.reporters.SolrJmxReporter}.
> + */
> +package org.apache.solr.metrics.reporters.jmx;
> 
> http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/54469c7c/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterCloudTest.java
> ----------------------------------------------------------------------
> diff --git a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterCloudTest.java b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterCloudTest.java
> new file mode 100644
> index 0000000..a76f483
> --- /dev/null
> +++ b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterCloudTest.java
> @@ -0,0 +1,122 @@
> +/*
> + * 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.reporters;
> +
> +import javax.management.MBeanServer;
> +import javax.management.MBeanServerFactory;
> +import javax.management.ObjectInstance;
> +import javax.management.Query;
> +import javax.management.QueryExp;
> +import java.lang.invoke.MethodHandles;
> +import java.lang.management.ManagementFactory;
> +import java.util.HashSet;
> +import java.util.Map;
> +import java.util.Set;
> +
> +import org.apache.solr.client.solrj.embedded.JettySolrRunner;
> +import org.apache.solr.client.solrj.impl.CloudSolrClient;
> +import org.apache.solr.client.solrj.request.CollectionAdminRequest;
> +import org.apache.solr.cloud.SolrCloudTestCase;
> +import org.apache.solr.common.SolrInputDocument;
> +import org.apache.solr.common.params.CommonParams;
> +import org.apache.solr.metrics.SolrMetricManager;
> +import org.apache.solr.metrics.SolrMetricReporter;
> +import org.apache.solr.metrics.reporters.jmx.JmxMetricsReporter;
> +import org.junit.AfterClass;
> +import org.junit.BeforeClass;
> +import org.junit.Test;
> +import org.slf4j.Logger;
> +import org.slf4j.LoggerFactory;
> +
> +/**
> + *
> + */
> +public class SolrJmxReporterCloudTest extends SolrCloudTestCase {
> +  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
> +
> +  private static MBeanServer mBeanServer;
> +  private static String COLLECTION = SolrJmxReporterCloudTest.class.getSimpleName() + "_collection";
> +
> +  @BeforeClass
> +  public static void setupCluster() throws Exception {
> +    // make sure there's an MBeanServer
> +    mBeanServer = ManagementFactory.getPlatformMBeanServer();
> +    configureCluster(1)
> +        .addConfig("conf", configset("cloud-minimal"))
> +        .configure();
> +    CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 1)
> +        .setMaxShardsPerNode(2)
> +        .process(cluster.getSolrClient());
> +  }
> +
> +  @AfterClass
> +  public static void releaseMBeanServer() throws Exception {
> +    if (mBeanServer != null) {
> +      MBeanServerFactory.releaseMBeanServer(mBeanServer);
> +    }
> +  }
> +
> +  @Test
> +  public void testJmxReporter() throws Exception {
> +    CollectionAdminRequest.reloadCollection(COLLECTION).processAndWait(cluster.getSolrClient(), 60);
> +    CloudSolrClient solrClient = cluster.getSolrClient();
> +    // index some docs
> +    for (int i = 0; i < 100; i++) {
> +      SolrInputDocument doc = new SolrInputDocument();
> +      doc.addField("id", "id-" + i);
> +      solrClient.add(COLLECTION, doc);
> +    }
> +    solrClient.commit(COLLECTION);
> +    // make sure searcher is present
> +    solrClient.query(COLLECTION, params(CommonParams.Q, "*:*"));
> +
> +    for (JettySolrRunner runner : cluster.getJettySolrRunners()) {
> +      SolrMetricManager manager = runner.getCoreContainer().getMetricManager();
> +      for (String registry : manager.registryNames()) {
> +        Map<String, SolrMetricReporter> reporters = manager.getReporters(registry);
> +        long jmxReporters = reporters.entrySet().stream().filter(e -> e.getValue() instanceof SolrJmxReporter).count();
> +        reporters.forEach((k, v) -> {
> +          if (!(v instanceof SolrJmxReporter)) {
> +            return;
> +          }
> +          if (!((SolrJmxReporter)v).getDomain().startsWith("solr.core")) {
> +            return;
> +          }
> +          if (!((SolrJmxReporter)v).isActive()) {
> +            return;
> +          }
> +          QueryExp exp = Query.eq(Query.attr(JmxMetricsReporter.INSTANCE_TAG), Query.value(Integer.toHexString(v.hashCode())));
> +          Set<ObjectInstance> beans = mBeanServer.queryMBeans(null, exp);
> +          if (((SolrJmxReporter) v).isStarted() && beans.isEmpty() && jmxReporters < 2) {
> +            log.info("DocCollection: " + getCollectionState(COLLECTION));
> +            fail("JMX reporter " + k + " for registry " + registry + " failed to register any beans!");
> +          } else {
> +            Set<String> categories = new HashSet<>();
> +            beans.forEach(bean -> {
> +              String cat = bean.getObjectName().getKeyProperty("category");
> +              if (cat != null) {
> +                categories.add(cat);
> +              }
> +            });
> +            log.info("Registered categories: " + categories);
> +            assertTrue("Too few categories: " + categories, categories.size() > 5);
> +          }
> +        });
> +      }
> +    }
> +  }
> +}
> 


Mime
View raw message