You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@lucene.apache.org by Andrzej Białecki <an...@lucidworks.com> on 2017/08/18 21:19:39 UTC
Re: lucene-solr:branch_6_6: SOLR-11221: SolrJmxReporter broken on
core reload.
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);
> + }
> + });
> + }
> + }
> + }
> +}
>